~ubuntu-branches/ubuntu/lucid/graphviz/lucid-updates

« back to all changes in this revision

Viewing changes to plugin/gdiplus/gvrender_gdiplus.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2008-06-19 20:23:23 UTC
  • mfrom: (1.2.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20080619202323-ls23h96ntj9ny94m
Tags: 2.18-1ubuntu1
* Merge from debian unstable, remaining changes:
  - Build depend on liblualib50-dev instead of liblua5.1-0-dev.
  - Drop libttf-dev (libttf-dev is in universe) (LP: #174749).
  - Replace gs-common with ghostscript.
  - Build-depend on python-dev instead of python2.4-dev or python2.5-dev.
  - Mention the correct python version for the python bindings in the
    package description.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* $Id: gvrender_gdiplus.cpp,v 1.2 2008/02/28 05:32:29 glenlow Exp $ $Revision: 1.2 $ */
 
2
/* vim:set shiftwidth=4 ts=8: */
 
3
 
 
4
/**********************************************************
 
5
*      This software is part of the graphviz package      *
 
6
*                http://www.graphviz.org/                 *
 
7
*                                                         *
 
8
*            Copyright (c) 1994-2004 AT&T Corp.           *
 
9
*                and is licensed under the                *
 
10
*            Common Public License, Version 1.0           *
 
11
*                      by AT&T Corp.                      *
 
12
*                                                         *
 
13
*        Information and Software Systems Research        *
 
14
*              AT&T Research, Florham Park NJ             *
 
15
**********************************************************/
 
16
 
 
17
#ifdef HAVE_CONFIG_H
 
18
#include "config.h"
 
19
#endif
 
20
 
 
21
#include <stdlib.h>
 
22
#include <string.h>
 
23
 
 
24
#include "gvplugin_device.h"
 
25
#include "gvplugin_render.h"
 
26
#include "graph.h"
 
27
 
 
28
#include <windows.h>
 
29
#include "GdiPlus.h"
 
30
 
 
31
#include <memory>
 
32
#include <vector>
 
33
 
 
34
extern "C" size_t gvdevice_write(GVJ_t *job, const unsigned char *s, unsigned int len);
 
35
 
 
36
using namespace std;
 
37
using namespace Gdiplus;
 
38
 
 
39
typedef enum {
 
40
        FORMAT_BMP,
 
41
        FORMAT_EMF,
 
42
        FORMAT_EMFPLUS,
 
43
        FORMAT_GIF,
 
44
        FORMAT_JPEG,
 
45
        FORMAT_PNG,
 
46
        FORMAT_TIFF
 
47
} format_type;
 
48
 
 
49
/* class id corresponding to each format_type */
 
50
static GUID format_id [] = {
 
51
        ImageFormatBMP,
 
52
        ImageFormatEMF,
 
53
        ImageFormatEMF,
 
54
        ImageFormatGIF,
 
55
        ImageFormatJPEG,
 
56
        ImageFormatPNG,
 
57
        ImageFormatTIFF
 
58
};
 
59
 
 
60
/* Graphics for internal use, so that we can record image etc. for subsequent retrieval off the job struct */
 
61
struct ImageGraphics: public Graphics
 
62
{
 
63
        ULONG_PTR token;
 
64
        Image *image;
 
65
        IStream *stream;
 
66
        
 
67
        ImageGraphics(ULONG_PTR startupToken, Image *newImage, IStream *newStream):
 
68
                Graphics(newImage), token(startupToken), image(newImage), stream(newStream)
 
69
        {
 
70
        }
 
71
};
 
72
 
 
73
/* RAII for GetDC/ReleaseDC */
 
74
struct DeviceContext
 
75
{
 
76
        HWND hwnd;
 
77
        HDC hdc;
 
78
        
 
79
        DeviceContext(HWND wnd = NULL): hwnd(wnd), hdc(GetDC(wnd))
 
80
        {
 
81
        }
 
82
        
 
83
        ~DeviceContext()
 
84
        {
 
85
                ReleaseDC(hwnd, hdc);
 
86
        }
 
87
 
 
88
};
 
89
 
 
90
static void gdiplusgen_begin_job(GVJ_t *job)
 
91
{
 
92
        if (!job->external_context)
 
93
                job->context = NULL;
 
94
}
 
95
 
 
96
static void gdiplusgen_end_job(GVJ_t *job)
 
97
{
 
98
        if (!job->external_context) {
 
99
                Graphics *context = (Graphics *)job->context;
 
100
                
 
101
                /* flush and delete the graphics */
 
102
                ImageGraphics *imageGraphics = static_cast<ImageGraphics *>(context);
 
103
                ULONG_PTR startupToken = imageGraphics->token;
 
104
                Image *image = imageGraphics->image;
 
105
                IStream *stream = imageGraphics->stream;
 
106
                delete imageGraphics;
 
107
                
 
108
                switch (job->device.id) {
 
109
                        case FORMAT_EMF:
 
110
                        case FORMAT_EMFPLUS:
 
111
                                break;
 
112
                        default:
 
113
                                /* search the encoders for one that matches our device id, then save the bitmap there */
 
114
                                Bitmap *bitmap = static_cast<Bitmap *>(image);
 
115
                                GUID formatId = format_id[job->device.id];
 
116
                                UINT encoderNum;
 
117
                                UINT encoderSize;
 
118
                                GetImageEncodersSize(&encoderNum, &encoderSize);
 
119
                                vector<char> codec_buffer(encoderSize);
 
120
                                ImageCodecInfo *codecs = (ImageCodecInfo *)&codec_buffer.front();
 
121
                                GetImageEncoders(encoderNum, encoderSize, codecs);
 
122
                                for (UINT i = 0; i < encoderNum; ++i)
 
123
                                        if (memcmp(&(format_id[job->device.id]), &codecs[i].FormatID, sizeof(GUID)) == 0) {
 
124
                                                bitmap->Save(stream, &codecs[i].Clsid, NULL);
 
125
                                                break;
 
126
                                        }
 
127
                                break;
 
128
                }
 
129
                
 
130
                delete image;   /* NOTE: in the case of EMF, this actually flushes out the image to the underlying stream */
 
131
 
 
132
                /* blast the streamed buffer back to the gvdevice */
 
133
                /* NOTE: this is somewhat inefficient since we should be streaming directly to gvdevice rather than buffering first */
 
134
                /* ... however, GDI+ requires any such direct IStream to implement Seek Read, Write, Stat methods and gvdevice really only offers a write-once model */
 
135
                HGLOBAL buffer = NULL;
 
136
                GetHGlobalFromStream(stream, &buffer);
 
137
                stream->Release();
 
138
                gvdevice_write(job, (unsigned char*)GlobalLock(buffer), GlobalSize(buffer));
 
139
                GlobalFree(buffer);
 
140
                        
 
141
                /* since this is an internal job, shut down GDI+ */
 
142
                GdiplusShutdown(startupToken);
 
143
        }
 
144
}
 
145
 
 
146
static void gdiplusgen_begin_page(GVJ_t *job)
 
147
{
 
148
        if (!job->external_context && !job->context) {
 
149
                /* since this is an internal job, start up GDI+ */
 
150
                ULONG_PTR startupToken;
 
151
                GdiplusStartupInput startupInput;
 
152
                GdiplusStartup(&startupToken, &startupInput, NULL);
 
153
                
 
154
                /* allocate memory and attach stream to it */
 
155
                HGLOBAL buffer = GlobalAlloc(GMEM_MOVEABLE, 0);
 
156
                IStream *stream = NULL;
 
157
                CreateStreamOnHGlobal(buffer, FALSE, &stream);  /* FALSE means don't deallocate buffer when releasing stream */
 
158
                
 
159
                Image *image;
 
160
                switch (job->device.id) {
 
161
                
 
162
                case FORMAT_EMF:
 
163
                case FORMAT_EMFPLUS:
 
164
                        /* EMF image */
 
165
                        image = new Metafile (stream,
 
166
                                DeviceContext().hdc,
 
167
                                RectF(0.0f, 0.0f, job->width, job->height),
 
168
                                MetafileFrameUnitPixel,
 
169
                                job->device.id == FORMAT_EMFPLUS ? EmfTypeEmfPlusOnly : EmfTypeEmfOnly);
 
170
                                /* output in EMF for wider compatibility; output in EMF+ for antialiasing etc. */
 
171
                        break;
 
172
                        
 
173
                default:
 
174
                        /* bitmap image */
 
175
                        image = new Bitmap (job->width, job->height, PixelFormat32bppARGB);
 
176
                        break;
 
177
                }
 
178
                
 
179
                job->context = new ImageGraphics(startupToken, image, stream);
 
180
        }
 
181
        
 
182
        /* start graphics state */
 
183
        Graphics *context = (Graphics *)job->context;
 
184
        context->SetSmoothingMode(SmoothingModeHighQuality);
 
185
        
 
186
        /* set up the context transformation */
 
187
        /* NOTE: we need to shift by height of graph and do a reflection before applying given transformations */
 
188
        context->ResetTransform();
 
189
        context->TranslateTransform(0, job->height);
 
190
        context->ScaleTransform(job->scale.x, -job->scale.y);
 
191
        context->RotateTransform(job->rotation);
 
192
        context->TranslateTransform(job->translation.x, job->translation.y);
 
193
}
 
194
 
 
195
static int CALLBACK fetch_first_font(
 
196
        const LOGFONTA *logFont,
 
197
        const TEXTMETRICA *textMetrics,
 
198
        DWORD fontType,
 
199
        LPARAM lParam)
 
200
{
 
201
        /* save the first font we see in the font enumeration */
 
202
        *((LOGFONTA *)lParam) = *logFont;
 
203
        return 0;
 
204
}
 
205
 
 
206
static auto_ptr<Font> find_font(char *fontname, double fontsize)
 
207
{
 
208
        /* search for a font with this name. if we can't find it, use the generic serif instead */
 
209
        /* NOTE: GDI font search is more comprehensive than GDI+ and will look for variants e.g. Arial Bold */
 
210
        DeviceContext reference;
 
211
        LOGFONTA font_to_find;
 
212
        font_to_find.lfCharSet = ANSI_CHARSET;
 
213
        strncpy(font_to_find.lfFaceName, fontname, sizeof(font_to_find.lfFaceName) - 1);
 
214
        font_to_find.lfFaceName[sizeof(font_to_find.lfFaceName) - 1] = '\0';
 
215
        font_to_find.lfPitchAndFamily = 0;
 
216
        LOGFONTA found_font;
 
217
        if (EnumFontFamiliesExA(reference.hdc,
 
218
                &font_to_find,
 
219
                fetch_first_font,
 
220
                (LPARAM)&found_font,
 
221
                0) == 0) {
 
222
                found_font.lfHeight = (LONG)-fontsize;
 
223
                return auto_ptr<Font>(new Font(reference.hdc, &found_font));
 
224
        }
 
225
        else
 
226
                return auto_ptr<Font>(new Font(FontFamily::GenericSerif(), fontsize));
 
227
}
 
228
 
 
229
static void gdiplusgen_textpara(GVJ_t *job, pointf p, textpara_t *para)
 
230
{
 
231
        /* convert incoming UTF8 string to wide chars */
 
232
        /* NOTE: conversion is 1 or more UTF8 chars to 1 wide char */
 
233
        vector<WCHAR> wide_str(strlen(para->str) + 1);
 
234
        int wide_count = MultiByteToWideChar(CP_UTF8, 0, para->str, -1, &wide_str.front(), wide_str.size());
 
235
        if (wide_count > 1) {
 
236
                /* adjust text position */
 
237
                switch (para->just) {
 
238
                case 'r':
 
239
                        p.x -= para->width;
 
240
                        break;
 
241
                case 'l':
 
242
                        p.x -= 0.0;
 
243
                        break;
 
244
                case 'n':
 
245
                default:
 
246
                        p.x -= para->width / 2.0;
 
247
                        break;
 
248
                }
 
249
                p.y -= para->yoffset_centerline;
 
250
 
 
251
                Graphics *context = (Graphics *)job->context;
 
252
 
 
253
                /* reverse the reflection done in begin_page */
 
254
                GraphicsState saved = context->Save();
 
255
                double center = para->fontsize / 2.0;
 
256
                context->TranslateTransform(p.x, p.y + center);
 
257
                context->ScaleTransform(1.0, -1.0);
 
258
 
 
259
                /* draw the text */
 
260
                SolidBrush brush(Color(job->obj->pencolor.u.rgba [3], job->obj->pencolor.u.rgba [0], job->obj->pencolor.u.rgba [1], job->obj->pencolor.u.rgba [2]));
 
261
                context->DrawString(&wide_str.front(), wide_count - 1, find_font(para->fontname, para->fontsize).get(), PointF(0, -center), &brush);
 
262
                context->Restore(saved);
 
263
        }
 
264
}
 
265
 
 
266
 
 
267
static vector<PointF> points(pointf *A, int n)
 
268
{
 
269
        /* convert Graphviz pointf (struct of double) to GDI+ PointF (struct of float) */
 
270
        vector<PointF> newPoints;
 
271
        for (int i = 0; i < n; ++i)
 
272
                newPoints.push_back(PointF(A[i].x, A[i].y));
 
273
        return newPoints;
 
274
}
 
275
 
 
276
static void gdiplusgen_path(GVJ_t *job, const GraphicsPath *path, int filled)
 
277
{
 
278
        Graphics *context = (Graphics *)job->context;
 
279
        
 
280
        /* fill the given path with job fill color */
 
281
        if (filled) {
 
282
                SolidBrush fill_brush(Color(job->obj->fillcolor.u.rgba [3], job->obj->fillcolor.u.rgba [0], job->obj->fillcolor.u.rgba [1], job->obj->fillcolor.u.rgba [2]));
 
283
                context->FillPath(&fill_brush, path);
 
284
        }
 
285
        
 
286
        /* draw the given path from job pen color and pen width */
 
287
        Pen draw_pen(Color(job->obj->pencolor.u.rgba [3], job->obj->pencolor.u.rgba [0], job->obj->pencolor.u.rgba [1], job->obj->pencolor.u.rgba [2]),
 
288
                job->obj->penwidth);
 
289
        context->DrawPath(&draw_pen, path);
 
290
}
 
291
 
 
292
static void gdiplusgen_ellipse(GVJ_t *job, pointf *A, int filled)
 
293
{
 
294
        /* convert ellipse into path */
 
295
        GraphicsPath path;
 
296
        double dx = A[1].x - A[0].x;
 
297
        double dy = A[1].y - A[0].y;
 
298
        path.AddEllipse(RectF(A[0].x - dx, A[0].y - dy, dx * 2.0, dy * 2.0));
 
299
        
 
300
        /* draw the path */
 
301
        gdiplusgen_path(job, &path, filled);
 
302
}
 
303
 
 
304
static void gdiplusgen_polygon(GVJ_t *job, pointf *A, int n, int filled)
 
305
{
 
306
        /* convert polygon into path */
 
307
        GraphicsPath path;
 
308
        path.AddPolygon(&points(A,n).front(), n);
 
309
        
 
310
        /* draw the path */
 
311
        gdiplusgen_path(job, &path, filled);
 
312
}
 
313
 
 
314
static void
 
315
gdiplusgen_bezier(GVJ_t *job, pointf *A, int n, int arrow_at_start,
 
316
             int arrow_at_end, int filled)
 
317
{
 
318
        /* convert the beziers into path */
 
319
        GraphicsPath path;
 
320
        path.AddBeziers(&points(A,n).front(), n);
 
321
        
 
322
        /* draw the path */
 
323
        gdiplusgen_path(job, &path, filled);
 
324
}
 
325
 
 
326
static void gdiplusgen_polyline(GVJ_t *job, pointf *A, int n)
 
327
{
 
328
        /* convert the lines into path */
 
329
        GraphicsPath path;
 
330
        path.AddLines(&points(A,n).front(), n);
 
331
        
 
332
        /* draw the path */
 
333
        gdiplusgen_path(job, &path, 0);
 
334
}
 
335
 
 
336
static gvrender_engine_t gdiplusgen_engine = {
 
337
    gdiplusgen_begin_job,
 
338
    gdiplusgen_end_job,
 
339
    0,                                                  /* gdiplusgen_begin_graph */
 
340
    0,                                                  /* gdiplusgen_end_graph */
 
341
    0,                                                  /* gdiplusgen_begin_layer */
 
342
    0,                                                  /* gdiplusgen_end_layer */
 
343
    gdiplusgen_begin_page,
 
344
    0,                                                  /* gdiplusgen_end_page */
 
345
    0,                                                  /* gdiplusgen_begin_cluster */
 
346
    0,                                                  /* gdiplusgen_end_cluster */
 
347
    0,                                                  /* gdiplusgen_begin_nodes */
 
348
    0,                                                  /* gdiplusgen_end_nodes */
 
349
    0,                                                  /* gdiplusgen_begin_edges */
 
350
    0,                                                  /* gdiplusgen_end_edges */
 
351
    0,                                                  /* gdiplusgen_begin_node */
 
352
    0,                                                  /* gdiplusgen_end_node */
 
353
    0,                                                  /* gdiplusgen_begin_edge */
 
354
    0,                                                  /* gdiplusgen_end_edge */
 
355
    0,                                                  /* gdiplusgen_begin_anchor */
 
356
    0,                                                  /* gdiplusgen_end_anchor */
 
357
    gdiplusgen_textpara,
 
358
    0,
 
359
    gdiplusgen_ellipse,
 
360
    gdiplusgen_polygon,
 
361
    gdiplusgen_bezier,
 
362
    gdiplusgen_polyline,
 
363
    0,                                                  /* gdiplusgen_comment */
 
364
    0,                                                  /* gdiplusgen_library_shape */
 
365
};
 
366
 
 
367
static gvrender_features_t render_features_gdiplus = {
 
368
        GVRENDER_DOES_TRANSFORM, /* flags */
 
369
    4.,                                                 /* default pad - graph units */
 
370
    NULL,                                               /* knowncolors */
 
371
    0,                                                  /* sizeof knowncolors */
 
372
    RGBA_BYTE                           /* color_type */
 
373
};
 
374
 
 
375
static gvdevice_features_t device_features_gdiplus = {
 
376
    GVDEVICE_BINARY_FORMAT
 
377
      | GVDEVICE_DOES_TRUECOLOR,/* flags */
 
378
    {0.,0.},                    /* default margin - points */
 
379
    {0.,0.},                    /* default page width, height - points */
 
380
    {96.,96.}                  /* dpi */
 
381
};
 
382
 
 
383
gvplugin_installed_t gvrender_gdiplus_types[] = {
 
384
    {0, "gdiplus", 1, &gdiplusgen_engine, &render_features_gdiplus},
 
385
    {0, NULL, 0, NULL, NULL}
 
386
};
 
387
 
 
388
gvplugin_installed_t gvdevice_gdiplus_types[] = {
 
389
        {FORMAT_BMP, "bmp:gdiplus", 8, NULL, &device_features_gdiplus},
 
390
        {FORMAT_EMF, "emf:gdiplus", 8, NULL, &device_features_gdiplus},
 
391
        {FORMAT_EMFPLUS, "emfplus:gdiplus", 8, NULL, &device_features_gdiplus},
 
392
        {FORMAT_GIF, "gif:gdiplus", 8, NULL, &device_features_gdiplus},
 
393
        {FORMAT_JPEG, "jpe:gdiplus", 8, NULL, &device_features_gdiplus},
 
394
        {FORMAT_JPEG, "jpeg:gdiplus", 8, NULL, &device_features_gdiplus},
 
395
        {FORMAT_JPEG, "jpg:gdiplus", 8, NULL, &device_features_gdiplus},
 
396
        {FORMAT_PNG, "png:gdiplus", 8, NULL, &device_features_gdiplus},
 
397
        {FORMAT_TIFF, "tif:gdiplus", 8, NULL, &device_features_gdiplus},
 
398
        {FORMAT_TIFF, "tiff:gdiplus", 8, NULL, &device_features_gdiplus},
 
399
        {0, NULL, 0, NULL, NULL}
 
400
};