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: */
4
/**********************************************************
5
* This software is part of the graphviz package *
6
* http://www.graphviz.org/ *
8
* Copyright (c) 1994-2004 AT&T Corp. *
9
* and is licensed under the *
10
* Common Public License, Version 1.0 *
13
* Information and Software Systems Research *
14
* AT&T Research, Florham Park NJ *
15
**********************************************************/
24
#include "gvplugin_device.h"
25
#include "gvplugin_render.h"
34
extern "C" size_t gvdevice_write(GVJ_t *job, const unsigned char *s, unsigned int len);
37
using namespace Gdiplus;
49
/* class id corresponding to each format_type */
50
static GUID format_id [] = {
60
/* Graphics for internal use, so that we can record image etc. for subsequent retrieval off the job struct */
61
struct ImageGraphics: public Graphics
67
ImageGraphics(ULONG_PTR startupToken, Image *newImage, IStream *newStream):
68
Graphics(newImage), token(startupToken), image(newImage), stream(newStream)
73
/* RAII for GetDC/ReleaseDC */
79
DeviceContext(HWND wnd = NULL): hwnd(wnd), hdc(GetDC(wnd))
90
static void gdiplusgen_begin_job(GVJ_t *job)
92
if (!job->external_context)
96
static void gdiplusgen_end_job(GVJ_t *job)
98
if (!job->external_context) {
99
Graphics *context = (Graphics *)job->context;
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;
108
switch (job->device.id) {
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];
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);
130
delete image; /* NOTE: in the case of EMF, this actually flushes out the image to the underlying stream */
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);
138
gvdevice_write(job, (unsigned char*)GlobalLock(buffer), GlobalSize(buffer));
141
/* since this is an internal job, shut down GDI+ */
142
GdiplusShutdown(startupToken);
146
static void gdiplusgen_begin_page(GVJ_t *job)
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);
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 */
160
switch (job->device.id) {
165
image = new Metafile (stream,
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. */
175
image = new Bitmap (job->width, job->height, PixelFormat32bppARGB);
179
job->context = new ImageGraphics(startupToken, image, stream);
182
/* start graphics state */
183
Graphics *context = (Graphics *)job->context;
184
context->SetSmoothingMode(SmoothingModeHighQuality);
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);
195
static int CALLBACK fetch_first_font(
196
const LOGFONTA *logFont,
197
const TEXTMETRICA *textMetrics,
201
/* save the first font we see in the font enumeration */
202
*((LOGFONTA *)lParam) = *logFont;
206
static auto_ptr<Font> find_font(char *fontname, double fontsize)
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;
217
if (EnumFontFamiliesExA(reference.hdc,
222
found_font.lfHeight = (LONG)-fontsize;
223
return auto_ptr<Font>(new Font(reference.hdc, &found_font));
226
return auto_ptr<Font>(new Font(FontFamily::GenericSerif(), fontsize));
229
static void gdiplusgen_textpara(GVJ_t *job, pointf p, textpara_t *para)
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) {
246
p.x -= para->width / 2.0;
249
p.y -= para->yoffset_centerline;
251
Graphics *context = (Graphics *)job->context;
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);
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);
267
static vector<PointF> points(pointf *A, int n)
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));
276
static void gdiplusgen_path(GVJ_t *job, const GraphicsPath *path, int filled)
278
Graphics *context = (Graphics *)job->context;
280
/* fill the given path with job fill color */
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);
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]),
289
context->DrawPath(&draw_pen, path);
292
static void gdiplusgen_ellipse(GVJ_t *job, pointf *A, int filled)
294
/* convert ellipse into 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));
301
gdiplusgen_path(job, &path, filled);
304
static void gdiplusgen_polygon(GVJ_t *job, pointf *A, int n, int filled)
306
/* convert polygon into path */
308
path.AddPolygon(&points(A,n).front(), n);
311
gdiplusgen_path(job, &path, filled);
315
gdiplusgen_bezier(GVJ_t *job, pointf *A, int n, int arrow_at_start,
316
int arrow_at_end, int filled)
318
/* convert the beziers into path */
320
path.AddBeziers(&points(A,n).front(), n);
323
gdiplusgen_path(job, &path, filled);
326
static void gdiplusgen_polyline(GVJ_t *job, pointf *A, int n)
328
/* convert the lines into path */
330
path.AddLines(&points(A,n).front(), n);
333
gdiplusgen_path(job, &path, 0);
336
static gvrender_engine_t gdiplusgen_engine = {
337
gdiplusgen_begin_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 */
363
0, /* gdiplusgen_comment */
364
0, /* gdiplusgen_library_shape */
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 */
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 */
383
gvplugin_installed_t gvrender_gdiplus_types[] = {
384
{0, "gdiplus", 1, &gdiplusgen_engine, &render_features_gdiplus},
385
{0, NULL, 0, NULL, NULL}
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}