wineps.drv: Ignore requested resolutions not supported by device.
[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 = NULL;
236     (*metafile)->bounds = *frameRect;
237     (*metafile)->unit = frameUnit;
238     (*metafile)->metafile_type = type;
239     (*metafile)->record_dc = record_dc;
240     (*metafile)->comment_data = NULL;
241     (*metafile)->comment_data_size = 0;
242     (*metafile)->comment_data_length = 0;
243     (*metafile)->hemf = NULL;
244
245     stat = METAFILE_WriteHeader(*metafile, hdc);
246
247     if (stat != Ok)
248     {
249         DeleteEnhMetaFile(CloseEnhMetaFile(record_dc));
250         GdipFree(*metafile);
251         *metafile = NULL;
252         return OutOfMemory;
253     }
254
255     return stat;
256 }
257
258 /*****************************************************************************
259  * GdipRecordMetafileI [GDIPLUS.@]
260  */
261 GpStatus WINGDIPAPI GdipRecordMetafileI(HDC hdc, EmfType type, GDIPCONST GpRect *frameRect,
262                                         MetafileFrameUnit frameUnit, GDIPCONST WCHAR *desc, GpMetafile **metafile)
263 {
264     GpRectF frameRectF, *pFrameRectF;
265
266     TRACE("(%p %d %p %d %p %p)\n", hdc, type, frameRect, frameUnit, desc, metafile);
267
268     if (frameRect)
269     {
270         frameRectF.X = frameRect->X;
271         frameRectF.Y = frameRect->Y;
272         frameRectF.Width = frameRect->Width;
273         frameRectF.Height = frameRect->Height;
274         pFrameRectF = &frameRectF;
275     }
276     else
277         pFrameRectF = NULL;
278
279     return GdipRecordMetafile(hdc, type, pFrameRectF, frameUnit, desc, metafile);
280 }
281
282 GpStatus METAFILE_GetGraphicsContext(GpMetafile* metafile, GpGraphics **result)
283 {
284     GpStatus stat;
285
286     if (!metafile->record_dc || metafile->record_graphics)
287         return InvalidParameter;
288
289     stat = graphics_from_image((GpImage*)metafile, &metafile->record_graphics);
290
291     if (stat == Ok)
292         *result = metafile->record_graphics;
293
294     return stat;
295 }
296
297 GpStatus METAFILE_GetDC(GpMetafile* metafile, HDC *hdc)
298 {
299     if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
300     {
301         EmfPlusRecordHeader *record;
302         GpStatus stat;
303
304         stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusRecordHeader), (void**)&record);
305         if (stat != Ok)
306             return stat;
307
308         record->Type = EmfPlusRecordTypeGetDC;
309         record->Flags = 0;
310
311         METAFILE_WriteRecords(metafile);
312     }
313
314     *hdc = metafile->record_dc;
315
316     return Ok;
317 }
318
319 GpStatus METAFILE_ReleaseDC(GpMetafile* metafile, HDC hdc)
320 {
321     if (hdc != metafile->record_dc)
322         return InvalidParameter;
323
324     return Ok;
325 }
326
327 GpStatus METAFILE_GraphicsDeleted(GpMetafile* metafile)
328 {
329     GpStatus stat;
330
331     stat = METAFILE_WriteEndOfFile(metafile);
332     metafile->record_graphics = NULL;
333
334     metafile->hemf = CloseEnhMetaFile(metafile->record_dc);
335     metafile->record_dc = NULL;
336
337     GdipFree(metafile->comment_data);
338     metafile->comment_data = NULL;
339     metafile->comment_data_size = 0;
340
341     return stat;
342 }
343
344 GpStatus WINGDIPAPI GdipGetHemfFromMetafile(GpMetafile *metafile, HENHMETAFILE *hEmf)
345 {
346     TRACE("(%p,%p)\n", metafile, hEmf);
347
348     if (!metafile || !hEmf || !metafile->hemf)
349         return InvalidParameter;
350
351     *hEmf = metafile->hemf;
352     metafile->hemf = NULL;
353
354     return Ok;
355 }
356
357 static GpStatus METAFILE_PlaybackGetDC(GpMetafile *metafile)
358 {
359     GpStatus stat = Ok;
360
361     stat = GdipGetDC(metafile->playback_graphics, &metafile->playback_dc);
362
363     if (stat == Ok)
364     {
365         /* The result of GdipGetDC always expects device co-ordinates, but the
366          * device co-ordinates of the source metafile do not correspond to
367          * device co-ordinates of the destination. Therefore, we set up the DC
368          * so that the metafile's bounds map to the destination points where we
369          * are drawing this metafile. */
370         SetMapMode(metafile->playback_dc, MM_ANISOTROPIC);
371
372         SetWindowOrgEx(metafile->playback_dc, metafile->bounds.X, metafile->bounds.Y, NULL);
373         SetWindowExtEx(metafile->playback_dc, metafile->bounds.Width, metafile->bounds.Height, NULL);
374
375         SetViewportOrgEx(metafile->playback_dc, metafile->playback_points[0].X, metafile->playback_points[0].Y, NULL);
376         SetViewportExtEx(metafile->playback_dc,
377             metafile->playback_points[1].X - metafile->playback_points[0].X,
378             metafile->playback_points[2].Y - metafile->playback_points[0].Y, NULL);
379     }
380
381     return stat;
382 }
383
384 static void METAFILE_PlaybackReleaseDC(GpMetafile *metafile)
385 {
386     if (metafile->playback_dc)
387     {
388         GdipReleaseDC(metafile->playback_graphics, metafile->playback_dc);
389         metafile->playback_dc = NULL;
390     }
391 }
392
393 GpStatus WINGDIPAPI GdipPlayMetafileRecord(GDIPCONST GpMetafile *metafile,
394     EmfPlusRecordType recordType, UINT flags, UINT dataSize, GDIPCONST BYTE *data)
395 {
396     TRACE("(%p,%x,%x,%d,%p)\n", metafile, recordType, flags, dataSize, data);
397
398     if (!metafile || (dataSize && !data) || !metafile->playback_graphics)
399         return InvalidParameter;
400
401     if (recordType >= 1 && recordType <= 0x7a)
402     {
403         /* regular EMF record */
404         if (metafile->playback_dc)
405         {
406             ENHMETARECORD *record;
407
408             record = GdipAlloc(dataSize + 8);
409
410             if (record)
411             {
412                 record->iType = recordType;
413                 record->nSize = dataSize;
414                 memcpy(record->dParm, data, dataSize);
415
416                 PlayEnhMetaFileRecord(metafile->playback_dc, metafile->handle_table,
417                     record, metafile->handle_count);
418
419                 GdipFree(record);
420             }
421             else
422                 return OutOfMemory;
423         }
424     }
425     else
426     {
427         METAFILE_PlaybackReleaseDC((GpMetafile*)metafile);
428
429         switch(recordType)
430         {
431         case EmfPlusRecordTypeHeader:
432         case EmfPlusRecordTypeEndOfFile:
433             break;
434         case EmfPlusRecordTypeGetDC:
435             METAFILE_PlaybackGetDC((GpMetafile*)metafile);
436             break;
437         default:
438             FIXME("Not implemented for record type %x\n", recordType);
439             return NotImplemented;
440         }
441     }
442
443     return Ok;
444 }
445
446 struct enum_metafile_data
447 {
448     EnumerateMetafileProc callback;
449     void *callback_data;
450     GpMetafile *metafile;
451 };
452
453 static int CALLBACK enum_metafile_proc(HDC hDC, HANDLETABLE *lpHTable, const ENHMETARECORD *lpEMFR,
454     int nObj, LPARAM lpData)
455 {
456     BOOL ret;
457     struct enum_metafile_data *data = (struct enum_metafile_data*)lpData;
458     const BYTE* pStr;
459
460     data->metafile->handle_table = lpHTable;
461     data->metafile->handle_count = nObj;
462
463     /* First check for an EMF+ record. */
464     if (lpEMFR->iType == EMR_GDICOMMENT)
465     {
466         const EMRGDICOMMENT *comment = (const EMRGDICOMMENT*)lpEMFR;
467
468         if (comment->cbData >= 4 && memcmp(comment->Data, "EMF+", 4) == 0)
469         {
470             int offset = 4;
471
472             while (offset + sizeof(EmfPlusRecordHeader) <= comment->cbData)
473             {
474                 const EmfPlusRecordHeader *record = (const EmfPlusRecordHeader*)&comment->Data[offset];
475
476                 if (record->DataSize)
477                     pStr = (const BYTE*)(record+1);
478                 else
479                     pStr = NULL;
480
481                 ret = data->callback(record->Type, record->Flags, record->DataSize,
482                     pStr, data->callback_data);
483
484                 if (!ret)
485                     return 0;
486
487                 offset += record->Size;
488             }
489
490             return 1;
491         }
492     }
493
494     if (lpEMFR->nSize != 8)
495         pStr = (const BYTE*)lpEMFR->dParm;
496     else
497         pStr = NULL;
498
499     return data->callback(lpEMFR->iType, 0, lpEMFR->nSize-8,
500         pStr, data->callback_data);
501 }
502
503 GpStatus WINGDIPAPI GdipEnumerateMetafileSrcRectDestPoints(GpGraphics *graphics,
504     GDIPCONST GpMetafile *metafile, GDIPCONST GpPointF *destPoints, INT count,
505     GDIPCONST GpRectF *srcRect, Unit srcUnit, EnumerateMetafileProc callback,
506     VOID *callbackData, GDIPCONST GpImageAttributes *imageAttributes)
507 {
508     struct enum_metafile_data data;
509     GpStatus stat;
510     GpMetafile *real_metafile = (GpMetafile*)metafile; /* whoever made this const was joking */
511
512     TRACE("(%p,%p,%p,%i,%p,%i,%p,%p,%p)\n", graphics, metafile,
513         destPoints, count, srcRect, srcUnit, callback, callbackData,
514         imageAttributes);
515
516     if (!graphics || !metafile || !destPoints || count != 3 || !srcRect)
517         return InvalidParameter;
518
519     if (!metafile->hemf)
520         return InvalidParameter;
521
522     if (metafile->playback_graphics)
523         return ObjectBusy;
524
525     TRACE("%s %i -> %s %s %s\n", debugstr_rectf(srcRect), srcUnit,
526         debugstr_pointf(&destPoints[0]), debugstr_pointf(&destPoints[1]),
527         debugstr_pointf(&destPoints[2]));
528
529     data.callback = callback;
530     data.callback_data = callbackData;
531     data.metafile = real_metafile;
532
533     real_metafile->playback_graphics = graphics;
534     real_metafile->playback_dc = NULL;
535
536     memcpy(real_metafile->playback_points, destPoints, sizeof(PointF) * 3);
537     stat = GdipTransformPoints(graphics, CoordinateSpaceDevice, CoordinateSpaceWorld, real_metafile->playback_points, 3);
538
539     if (stat == Ok && metafile->metafile_type == MetafileTypeEmf)
540         stat = METAFILE_PlaybackGetDC((GpMetafile*)metafile);
541
542     if (stat == Ok)
543         EnumEnhMetaFile(0, metafile->hemf, enum_metafile_proc, &data, NULL);
544
545     METAFILE_PlaybackReleaseDC((GpMetafile*)metafile);
546
547     real_metafile->playback_graphics = NULL;
548
549     return stat;
550 }
551
552 static int CALLBACK get_metafile_type_proc(HDC hDC, HANDLETABLE *lpHTable, const ENHMETARECORD *lpEMFR,
553     int nObj, LPARAM lpData)
554 {
555     MetafileType *result = (MetafileType*)lpData;
556
557     if (lpEMFR->iType == EMR_GDICOMMENT)
558     {
559         const EMRGDICOMMENT *comment = (const EMRGDICOMMENT*)lpEMFR;
560
561         if (comment->cbData >= 4 && memcmp(comment->Data, "EMF+", 4) == 0)
562         {
563             const EmfPlusRecordHeader *header = (const EmfPlusRecordHeader*)&comment->Data[4];
564
565             if (4 + sizeof(EmfPlusRecordHeader) <= comment->cbData &&
566                 header->Type == EmfPlusRecordTypeHeader)
567             {
568                 if ((header->Flags & 1) == 1)
569                     *result = MetafileTypeEmfPlusDual;
570                 else
571                     *result = MetafileTypeEmfPlusOnly;
572             }
573         }
574         else
575             *result = MetafileTypeEmf;
576     }
577     else
578         *result = MetafileTypeEmf;
579
580     return FALSE;
581 }
582
583 MetafileType METAFILE_GetEmfType(HENHMETAFILE hemf)
584 {
585     MetafileType result = MetafileTypeInvalid;
586     EnumEnhMetaFile(NULL, hemf, get_metafile_type_proc, &result, NULL);
587     return result;
588 }