gdiplus: Avoid out of bounds access warning.
[wine] / dlls / gdiplus / metafile.c
1 /*
2  * Copyright (C) 2011 Vincent Povirk for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include <stdarg.h>
20 #include <math.h>
21
22 #include "windef.h"
23 #include "winbase.h"
24 #include "wingdi.h"
25 #include "wine/unicode.h"
26
27 #define COBJMACROS
28 #include "objbase.h"
29 #include "ocidl.h"
30 #include "olectl.h"
31 #include "ole2.h"
32
33 #include "winreg.h"
34 #include "shlwapi.h"
35
36 #include "gdiplus.h"
37 #include "gdiplus_private.h"
38 #include "wine/debug.h"
39 #include "wine/list.h"
40
41 WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
42
43 typedef struct EmfPlusRecordHeader
44 {
45     WORD Type;
46     WORD Flags;
47     DWORD Size;
48     DWORD DataSize;
49 } EmfPlusRecordHeader;
50
51 typedef struct EmfPlusHeader
52 {
53     EmfPlusRecordHeader Header;
54     DWORD Version;
55     DWORD EmfPlusFlags;
56     DWORD LogicalDpiX;
57     DWORD LogicalDpiY;
58 } EmfPlusHeader;
59
60 static GpStatus METAFILE_AllocateRecord(GpMetafile *metafile, DWORD size, void **result)
61 {
62     DWORD size_needed;
63     EmfPlusRecordHeader *record;
64
65     if (!metafile->comment_data_size)
66     {
67         DWORD data_size = max(256, size * 2 + 4);
68         metafile->comment_data = GdipAlloc(data_size);
69
70         if (!metafile->comment_data)
71             return OutOfMemory;
72
73         memcpy(metafile->comment_data, "EMF+", 4);
74
75         metafile->comment_data_size = data_size;
76         metafile->comment_data_length = 4;
77     }
78
79     size_needed = size + metafile->comment_data_length;
80
81     if (size_needed > metafile->comment_data_size)
82     {
83         DWORD data_size = size_needed * 2;
84         BYTE *new_data = GdipAlloc(data_size);
85
86         if (!new_data)
87             return OutOfMemory;
88
89         memcpy(new_data, metafile->comment_data, metafile->comment_data_length);
90
91         metafile->comment_data_size = data_size;
92         GdipFree(metafile->comment_data);
93         metafile->comment_data = new_data;
94     }
95
96     *result = metafile->comment_data + metafile->comment_data_length;
97     metafile->comment_data_length += size;
98
99     record = (EmfPlusRecordHeader*)*result;
100     record->Size = size;
101     record->DataSize = size - sizeof(EmfPlusRecordHeader);
102
103     return Ok;
104 }
105
106 static void METAFILE_WriteRecords(GpMetafile *metafile)
107 {
108     if (metafile->comment_data_length > 4)
109     {
110         GdiComment(metafile->record_dc, metafile->comment_data_length, metafile->comment_data);
111         metafile->comment_data_length = 4;
112     }
113 }
114
115 static GpStatus METAFILE_WriteHeader(GpMetafile *metafile, HDC hdc)
116 {
117     GpStatus stat;
118
119     if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
120     {
121         EmfPlusHeader *header;
122
123         stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusHeader), (void**)&header);
124         if (stat != Ok)
125             return stat;
126
127         header->Header.Type = EmfPlusRecordTypeHeader;
128
129         if (metafile->metafile_type == MetafileTypeEmfPlusDual)
130             header->Header.Flags = 1;
131         else
132             header->Header.Flags = 0;
133
134         header->Version = 0xDBC01002;
135
136         if (GetDeviceCaps(hdc, TECHNOLOGY) == DT_RASDISPLAY)
137             header->EmfPlusFlags = 1;
138         else
139             header->EmfPlusFlags = 0;
140
141         header->LogicalDpiX = GetDeviceCaps(hdc, LOGPIXELSX);
142         header->LogicalDpiY = GetDeviceCaps(hdc, LOGPIXELSY);
143
144         METAFILE_WriteRecords(metafile);
145     }
146
147     return Ok;
148 }
149
150 static GpStatus METAFILE_WriteEndOfFile(GpMetafile *metafile)
151 {
152     GpStatus stat;
153
154     if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
155     {
156         EmfPlusRecordHeader *record;
157
158         stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusRecordHeader), (void**)&record);
159         if (stat != Ok)
160             return stat;
161
162         record->Type = EmfPlusRecordTypeEndOfFile;
163         record->Flags = 0;
164
165         METAFILE_WriteRecords(metafile);
166     }
167
168     return Ok;
169 }
170
171 GpStatus WINGDIPAPI GdipRecordMetafile(HDC hdc, EmfType type, GDIPCONST GpRectF *frameRect,
172                                        MetafileFrameUnit frameUnit, GDIPCONST WCHAR *desc, GpMetafile **metafile)
173 {
174     HDC record_dc;
175     REAL framerect_factor_x, framerect_factor_y;
176     RECT rc;
177     GpStatus stat;
178
179     TRACE("(%p %d %p %d %p %p)\n", hdc, type, frameRect, frameUnit, desc, metafile);
180
181     if (!hdc || type < EmfTypeEmfOnly || type > EmfTypeEmfPlusDual || !metafile)
182         return InvalidParameter;
183
184     if (!frameRect)
185     {
186         FIXME("not implemented for NULL rect\n");
187         return NotImplemented;
188     }
189
190     switch (frameUnit)
191     {
192     case MetafileFrameUnitPixel:
193         framerect_factor_x = 2540.0 / GetDeviceCaps(hdc, LOGPIXELSX);
194         framerect_factor_y = 2540.0 / GetDeviceCaps(hdc, LOGPIXELSY);
195         break;
196     case MetafileFrameUnitPoint:
197         framerect_factor_x = framerect_factor_y = 2540.0 / 72.0;
198         break;
199     case MetafileFrameUnitInch:
200         framerect_factor_x = framerect_factor_y = 2540.0;
201         break;
202     case MetafileFrameUnitDocument:
203         framerect_factor_x = framerect_factor_y = 2540.0 / 300.0;
204         break;
205     case MetafileFrameUnitMillimeter:
206         framerect_factor_x = framerect_factor_y = 100.0;
207         break;
208     case MetafileFrameUnitGdi:
209         framerect_factor_x = framerect_factor_y = 1.0;
210         break;
211     default:
212         return InvalidParameter;
213     }
214
215     rc.left = framerect_factor_x * frameRect->X;
216     rc.top = framerect_factor_y * frameRect->Y;
217     rc.right = rc.left + framerect_factor_x * frameRect->Width;
218     rc.bottom = rc.top + framerect_factor_y * frameRect->Height;
219
220     record_dc = CreateEnhMetaFileW(hdc, NULL, &rc, desc);
221
222     if (!record_dc)
223         return GenericError;
224
225     *metafile = GdipAlloc(sizeof(GpMetafile));
226     if(!*metafile)
227     {
228         DeleteEnhMetaFile(CloseEnhMetaFile(record_dc));
229         return OutOfMemory;
230     }
231
232     (*metafile)->image.type = ImageTypeMetafile;
233     (*metafile)->image.picture = NULL;
234     (*metafile)->image.flags   = ImageFlagsNone;
235     (*metafile)->image.palette_flags = 0;
236     (*metafile)->image.palette_count = 0;
237     (*metafile)->image.palette_size = 0;
238     (*metafile)->image.palette_entries = NULL;
239     (*metafile)->bounds = *frameRect;
240     (*metafile)->unit = frameUnit;
241     (*metafile)->metafile_type = type;
242     (*metafile)->record_dc = record_dc;
243     (*metafile)->comment_data = NULL;
244     (*metafile)->comment_data_size = 0;
245     (*metafile)->comment_data_length = 0;
246     (*metafile)->hemf = NULL;
247
248     stat = METAFILE_WriteHeader(*metafile, hdc);
249
250     if (stat != Ok)
251     {
252         DeleteEnhMetaFile(CloseEnhMetaFile(record_dc));
253         GdipFree(*metafile);
254         *metafile = NULL;
255         return OutOfMemory;
256     }
257
258     return stat;
259 }
260
261 /*****************************************************************************
262  * GdipRecordMetafileI [GDIPLUS.@]
263  */
264 GpStatus WINGDIPAPI GdipRecordMetafileI(HDC hdc, EmfType type, GDIPCONST GpRect *frameRect,
265                                         MetafileFrameUnit frameUnit, GDIPCONST WCHAR *desc, GpMetafile **metafile)
266 {
267     GpRectF frameRectF, *pFrameRectF;
268
269     TRACE("(%p %d %p %d %p %p)\n", hdc, type, frameRect, frameUnit, desc, metafile);
270
271     if (frameRect)
272     {
273         frameRectF.X = frameRect->X;
274         frameRectF.Y = frameRect->Y;
275         frameRectF.Width = frameRect->Width;
276         frameRectF.Height = frameRect->Height;
277         pFrameRectF = &frameRectF;
278     }
279     else
280         pFrameRectF = NULL;
281
282     return GdipRecordMetafile(hdc, type, pFrameRectF, frameUnit, desc, metafile);
283 }
284
285 GpStatus METAFILE_GetGraphicsContext(GpMetafile* metafile, GpGraphics **result)
286 {
287     GpStatus stat;
288
289     if (!metafile->record_dc || metafile->record_graphics)
290         return InvalidParameter;
291
292     stat = graphics_from_image((GpImage*)metafile, &metafile->record_graphics);
293
294     if (stat == Ok)
295         *result = metafile->record_graphics;
296
297     return stat;
298 }
299
300 GpStatus METAFILE_GetDC(GpMetafile* metafile, HDC *hdc)
301 {
302     if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
303     {
304         EmfPlusRecordHeader *record;
305         GpStatus stat;
306
307         stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusRecordHeader), (void**)&record);
308         if (stat != Ok)
309             return stat;
310
311         record->Type = EmfPlusRecordTypeGetDC;
312         record->Flags = 0;
313
314         METAFILE_WriteRecords(metafile);
315     }
316
317     *hdc = metafile->record_dc;
318
319     return Ok;
320 }
321
322 GpStatus METAFILE_ReleaseDC(GpMetafile* metafile, HDC hdc)
323 {
324     if (hdc != metafile->record_dc)
325         return InvalidParameter;
326
327     return Ok;
328 }
329
330 GpStatus METAFILE_GraphicsDeleted(GpMetafile* metafile)
331 {
332     GpStatus stat;
333
334     stat = METAFILE_WriteEndOfFile(metafile);
335     metafile->record_graphics = NULL;
336
337     metafile->hemf = CloseEnhMetaFile(metafile->record_dc);
338     metafile->record_dc = NULL;
339
340     GdipFree(metafile->comment_data);
341     metafile->comment_data = NULL;
342     metafile->comment_data_size = 0;
343
344     return stat;
345 }
346
347 GpStatus WINGDIPAPI GdipGetHemfFromMetafile(GpMetafile *metafile, HENHMETAFILE *hEmf)
348 {
349     TRACE("(%p,%p)\n", metafile, hEmf);
350
351     if (!metafile || !hEmf || !metafile->hemf)
352         return InvalidParameter;
353
354     *hEmf = metafile->hemf;
355     metafile->hemf = NULL;
356
357     return Ok;
358 }
359
360 static GpStatus METAFILE_PlaybackGetDC(GpMetafile *metafile)
361 {
362     GpStatus stat = Ok;
363
364     stat = GdipGetDC(metafile->playback_graphics, &metafile->playback_dc);
365
366     if (stat == Ok)
367     {
368         /* The result of GdipGetDC always expects device co-ordinates, but the
369          * device co-ordinates of the source metafile do not correspond to
370          * device co-ordinates of the destination. Therefore, we set up the DC
371          * so that the metafile's bounds map to the destination points where we
372          * are drawing this metafile. */
373         SetMapMode(metafile->playback_dc, MM_ANISOTROPIC);
374
375         SetWindowOrgEx(metafile->playback_dc, metafile->bounds.X, metafile->bounds.Y, NULL);
376         SetWindowExtEx(metafile->playback_dc, metafile->bounds.Width, metafile->bounds.Height, NULL);
377
378         SetViewportOrgEx(metafile->playback_dc, metafile->playback_points[0].X, metafile->playback_points[0].Y, NULL);
379         SetViewportExtEx(metafile->playback_dc,
380             metafile->playback_points[1].X - metafile->playback_points[0].X,
381             metafile->playback_points[2].Y - metafile->playback_points[0].Y, NULL);
382     }
383
384     return stat;
385 }
386
387 static void METAFILE_PlaybackReleaseDC(GpMetafile *metafile)
388 {
389     if (metafile->playback_dc)
390     {
391         GdipReleaseDC(metafile->playback_graphics, metafile->playback_dc);
392         metafile->playback_dc = NULL;
393     }
394 }
395
396 GpStatus WINGDIPAPI GdipPlayMetafileRecord(GDIPCONST GpMetafile *metafile,
397     EmfPlusRecordType recordType, UINT flags, UINT dataSize, GDIPCONST BYTE *data)
398 {
399     TRACE("(%p,%x,%x,%d,%p)\n", metafile, recordType, flags, dataSize, data);
400
401     if (!metafile || (dataSize && !data) || !metafile->playback_graphics)
402         return InvalidParameter;
403
404     if (recordType >= 1 && recordType <= 0x7a)
405     {
406         /* regular EMF record */
407         if (metafile->playback_dc)
408         {
409             ENHMETARECORD *record;
410
411             record = GdipAlloc(dataSize + 8);
412
413             if (record)
414             {
415                 record->iType = recordType;
416                 record->nSize = dataSize;
417                 memcpy(record->dParm, data, dataSize);
418
419                 PlayEnhMetaFileRecord(metafile->playback_dc, metafile->handle_table,
420                     record, metafile->handle_count);
421
422                 GdipFree(record);
423             }
424             else
425                 return OutOfMemory;
426         }
427     }
428     else
429     {
430         METAFILE_PlaybackReleaseDC((GpMetafile*)metafile);
431
432         switch(recordType)
433         {
434         case EmfPlusRecordTypeHeader:
435         case EmfPlusRecordTypeEndOfFile:
436             break;
437         case EmfPlusRecordTypeGetDC:
438             METAFILE_PlaybackGetDC((GpMetafile*)metafile);
439             break;
440         default:
441             FIXME("Not implemented for record type %x\n", recordType);
442             return NotImplemented;
443         }
444     }
445
446     return Ok;
447 }
448
449 struct enum_metafile_data
450 {
451     EnumerateMetafileProc callback;
452     void *callback_data;
453     GpMetafile *metafile;
454 };
455
456 static int CALLBACK enum_metafile_proc(HDC hDC, HANDLETABLE *lpHTable, const ENHMETARECORD *lpEMFR,
457     int nObj, LPARAM lpData)
458 {
459     BOOL ret;
460     struct enum_metafile_data *data = (struct enum_metafile_data*)lpData;
461     const BYTE* pStr;
462
463     data->metafile->handle_table = lpHTable;
464     data->metafile->handle_count = nObj;
465
466     /* First check for an EMF+ record. */
467     if (lpEMFR->iType == EMR_GDICOMMENT)
468     {
469         const EMRGDICOMMENT *comment = (const EMRGDICOMMENT*)lpEMFR;
470
471         if (comment->cbData >= 4 && memcmp(comment->Data, "EMF+", 4) == 0)
472         {
473             int offset = 4;
474
475             while (offset + sizeof(EmfPlusRecordHeader) <= comment->cbData)
476             {
477                 const EmfPlusRecordHeader *record = (const EmfPlusRecordHeader*)&comment->Data[offset];
478
479                 if (record->DataSize)
480                     pStr = (const BYTE*)(record+1);
481                 else
482                     pStr = NULL;
483
484                 ret = data->callback(record->Type, record->Flags, record->DataSize,
485                     pStr, data->callback_data);
486
487                 if (!ret)
488                     return 0;
489
490                 offset += record->Size;
491             }
492
493             return 1;
494         }
495     }
496
497     if (lpEMFR->nSize != 8)
498         pStr = (const BYTE*)lpEMFR->dParm;
499     else
500         pStr = NULL;
501
502     return data->callback(lpEMFR->iType, 0, lpEMFR->nSize-8,
503         pStr, data->callback_data);
504 }
505
506 GpStatus WINGDIPAPI GdipEnumerateMetafileSrcRectDestPoints(GpGraphics *graphics,
507     GDIPCONST GpMetafile *metafile, GDIPCONST GpPointF *destPoints, INT count,
508     GDIPCONST GpRectF *srcRect, Unit srcUnit, EnumerateMetafileProc callback,
509     VOID *callbackData, GDIPCONST GpImageAttributes *imageAttributes)
510 {
511     struct enum_metafile_data data;
512     GpStatus stat;
513     GpMetafile *real_metafile = (GpMetafile*)metafile; /* whoever made this const was joking */
514
515     TRACE("(%p,%p,%p,%i,%p,%i,%p,%p,%p)\n", graphics, metafile,
516         destPoints, count, srcRect, srcUnit, callback, callbackData,
517         imageAttributes);
518
519     if (!graphics || !metafile || !destPoints || count != 3 || !srcRect)
520         return InvalidParameter;
521
522     if (!metafile->hemf)
523         return InvalidParameter;
524
525     if (metafile->playback_graphics)
526         return ObjectBusy;
527
528     TRACE("%s %i -> %s %s %s\n", debugstr_rectf(srcRect), srcUnit,
529         debugstr_pointf(&destPoints[0]), debugstr_pointf(&destPoints[1]),
530         debugstr_pointf(&destPoints[2]));
531
532     data.callback = callback;
533     data.callback_data = callbackData;
534     data.metafile = real_metafile;
535
536     real_metafile->playback_graphics = graphics;
537     real_metafile->playback_dc = NULL;
538
539     memcpy(real_metafile->playback_points, destPoints, sizeof(PointF) * 3);
540     stat = GdipTransformPoints(graphics, CoordinateSpaceDevice, CoordinateSpaceWorld, real_metafile->playback_points, 3);
541
542     if (stat == Ok && metafile->metafile_type == MetafileTypeEmf)
543         stat = METAFILE_PlaybackGetDC((GpMetafile*)metafile);
544
545     if (stat == Ok)
546         EnumEnhMetaFile(0, metafile->hemf, enum_metafile_proc, &data, NULL);
547
548     METAFILE_PlaybackReleaseDC((GpMetafile*)metafile);
549
550     real_metafile->playback_graphics = NULL;
551
552     return stat;
553 }