]> granicus.if.org Git - graphviz/commitdiff
GDI+ textlayout; startup and shutdown GDI+ correctly when used outside of plugin...
authorglenlow <devnull@localhost>
Mon, 25 May 2009 06:52:18 +0000 (06:52 +0000)
committerglenlow <devnull@localhost>
Mon, 25 May 2009 06:52:18 +0000 (06:52 +0000)
plugin/gdiplus/Makefile.am
plugin/gdiplus/gvdevice_gdiplus.cpp
plugin/gdiplus/gvplugin_gdiplus.cpp
plugin/gdiplus/gvplugin_gdiplus.h
plugin/gdiplus/gvrender_gdiplus.cpp
plugin/gdiplus/gvtextlayout_gdiplus.cpp [new file with mode: 0755]

index 32496e3cc0aef19d9f65b0c506e3181ba3321f45..8892a7ee4b42dcbd6e0e5bd101b7eb1ca6e2ba94 100644 (file)
@@ -29,7 +29,8 @@ libgvplugin_gdiplus_C_la_SOURCES = \
        gvdevice_gdiplus.cpp \
        gvloadimage_gdiplus.cpp \
        gvplugin_gdiplus.cpp \
-       gvrender_gdiplus.cpp
+       gvrender_gdiplus.cpp \
+       gvtextlayout_gdiplus.cpp
 
 # libtool doesn't want to run with actual Windows import libs, so we force GdiPlus.lib through to the linker
 libgvplugin_gdiplus_la_LDFLAGS = -version-info @GVPLUGIN_VERSION_INFO@ -Wl,"$(PLATFORMSDKLIB)\GdiPlus.lib"
index 396d8bda802de17bcc4b8f4818b90cc20fa7bbcf..5ca93560bc414c4d521fac061f150cbe4dfdbe93 100755 (executable)
@@ -41,9 +41,6 @@ static void gdiplus_format(GVJ_t *job)
        IStream *stream = NULL;
        CreateStreamOnHGlobal(buffer, FALSE, &stream);  /* FALSE means don't deallocate buffer when releasing stream */
        
-       /* since this is an internal job, start up GDI+ */
-       GraphicsContext context;
-
        Bitmap bitmap(
                job->width,                                             /* width in pixels */
                job->height,                                    /* height in pixels */
index e6adc1ca01f28aa1ea8ce31fce9ede9b616f248a..a36aec144de9dcf918938dee0879625b8e52b713 100755 (executable)
 
 #include "gvplugin_gdiplus.h"
 
-#include <vector>
-
 extern gvplugin_installed_t gvrender_gdiplus_types[];
-// extern gvplugin_installed_t gvtextlayout_gdiplus_types[];
+extern gvplugin_installed_t gvtextlayout_gdiplus_types[];
 extern gvplugin_installed_t gvloadimage_gdiplus_types[];
 extern gvplugin_installed_t gvdevice_gdiplus_types[];
 extern gvplugin_installed_t gvdevice_gdiplus_types_for_cairo[];
@@ -29,19 +27,9 @@ extern gvplugin_installed_t gvdevice_gdiplus_types_for_cairo[];
 using namespace std;
 using namespace Gdiplus;
 
-GraphicsContext::GraphicsContext()
-{
-       GdiplusStartupInput startupInput;
-       GdiplusStartup(&token, &startupInput, NULL);
-}
-
-GraphicsContext::~GraphicsContext()
-{
-       GdiplusShutdown(token);
-}
-
 /* class id corresponding to each format_type */
 static GUID format_id [] = {
+       GUID_NULL,
        GUID_NULL,
        ImageFormatBMP,
        ImageFormatEMF,
@@ -52,6 +40,30 @@ static GUID format_id [] = {
        ImageFormatTIFF
 };
 
+static ULONG_PTR _gdiplusToken = NULL;
+
+static void UnuseGdiplus()
+{
+       GdiplusShutdown(_gdiplusToken);
+}
+
+void UseGdiplus()
+{
+       /* only startup once, and ensure we get shutdown */
+       if (!_gdiplusToken)
+       {
+               GdiplusStartupInput input;
+               GdiplusStartup(&_gdiplusToken, &input, NULL);
+               atexit(&UnuseGdiplus);
+       }
+}
+
+const Gdiplus::StringFormat* GetGenericTypographic()
+{
+       const Gdiplus::StringFormat* format = StringFormat::GenericTypographic();
+       return format;
+}
+
 void SaveBitmapToStream(Bitmap &bitmap, IStream *stream, int format)
 {
        /* search the encoders for one that matches our device id, then save the bitmap there */
@@ -70,16 +82,13 @@ void SaveBitmapToStream(Bitmap &bitmap, IStream *stream, int format)
 
 static gvplugin_api_t apis[] = {
     {API_render, gvrender_gdiplus_types},
-  //  {API_textlayout, gvtextlayout_gdiplus_types},
+       {API_textlayout, gvtextlayout_gdiplus_types},
        {API_loadimage, gvloadimage_gdiplus_types},
     {API_device, gvdevice_gdiplus_types},
        {API_device, gvdevice_gdiplus_types_for_cairo},
     {(api_t)0, 0},
 };
 
-
-
-
 #ifdef WIN32_DLL /*visual studio*/
 #ifndef GVPLUGIN_GDIPLUS_EXPORTS
 __declspec(dllimport) gvplugin_library_t gvplugin_gdiplus_LTX_library = { "gdiplus", apis };
@@ -90,7 +99,7 @@ __declspec(dllexport) gvplugin_library_t gvplugin_gdiplus_LTX_library = { "gdipl
 #ifdef GVDLL
 __declspec(dllexport) gvplugin_library_t gvplugin_gdiplus_LTX_library = { "gdiplus", apis };
 #else
-gvplugin_library_t gvplugin_library_t gvplugin_gdiplus_LTX_library = { "gdiplus", apis };
+extern "C" gvplugin_library_t gvplugin_gdiplus_LTX_library = { "gdiplus", apis };
 #endif
 #endif
 
index 5cbf359d8e74633e8d8fe6a90554c74bc5625533..f8607168765e9a871863ea3792bdc31d7dfb44d7 100755 (executable)
 #ifndef GVPLUGIN_GDIPLUS_H
 #define GVPLUGIN_GDIPLUS_H
 
+#include <vector>
+
 #include <Windows.h>
 #include <GdiPlus.h>
 
 typedef enum {
        FORMAT_NONE,
+       FORMAT_METAFILE,
        FORMAT_BMP,
        FORMAT_EMF,
        FORMAT_EMFPLUS,
@@ -31,23 +34,41 @@ typedef enum {
        FORMAT_TIFF
 } format_type;
 
-struct GraphicsContext
+/* RAII for GetDC/ReleaseDC */
+
+struct DeviceContext
+{
+       HWND hwnd;
+       HDC hdc;
+       
+       DeviceContext(HWND wnd = NULL): hwnd(wnd), hdc(GetDC(wnd))
+       {
+       }
+       
+       ~DeviceContext()
+       {
+               ReleaseDC(hwnd, hdc);
+       }
+
+};
+
+/* textlayout etc. */
+
+struct Layout
 {
-       ULONG_PTR token;
+       Gdiplus::Font* font;
+       std::vector<WCHAR> text;
        
-       GraphicsContext();
-       ~GraphicsContext();
+       Layout(char *fontname, double fontsize, char* string);
+       ~Layout();
 };
 
 static const int BYTES_PER_PIXEL = 4;          /* bytes per pixel */
 
-#define ADD_ATTR(a) \
-  if (a) { \
-        strcat(buf, comma ? " " : ", "); \
-        comma = 1; \
-        strcat(buf, a); \
-  }
+void gdiplus_free_layout(void *layout);
 
-extern void SaveBitmapToStream(Gdiplus::Bitmap &bitmap, IStream *stream, int format);
+void UseGdiplus();
+const Gdiplus::StringFormat* GetGenericTypographic();
+void SaveBitmapToStream(Gdiplus::Bitmap &bitmap, IStream *stream, int format);
 
 #endif
index a2b486c9aeeaccfba5d1f0ff3e04a7d55f52c623..13c4b4cde044a42188b8cad2d9a2e1ff00695f25 100755 (executable)
@@ -34,66 +34,39 @@ extern "C" size_t gvwrite(GVJ_t *job, const unsigned char *s, unsigned int len);
 using namespace std;
 using namespace Gdiplus;
 
-
-
-static char* gdiplus_psfontResolve (PostscriptAlias* pa)
-{
-    static char buf[1024];
-    int comma=0;
-    strcpy(buf, pa->family);
-
-    ADD_ATTR(pa->weight);
-    ADD_ATTR(pa->stretch);
-    ADD_ATTR(pa->style);
-   
-    return buf;
-}
-
-
 /* Graphics for internal use, so that we can record image etc. for subsequent retrieval off the job struct */
 struct ImageGraphics: public Graphics
 {
-       GraphicsContext *context;
        Image *image;
        IStream *stream;
        
-       ImageGraphics(GraphicsContext *newContext, Image *newImage, IStream *newStream):
-               Graphics(newImage), context(newContext), image(newImage), stream(newStream)
-       {
-       }
-};
-
-/* RAII for GetDC/ReleaseDC */
-struct DeviceContext
-{
-       HWND hwnd;
-       HDC hdc;
-       
-       DeviceContext(HWND wnd = NULL): hwnd(wnd), hdc(GetDC(wnd))
-       {
-       }
-       
-       ~DeviceContext()
+       ImageGraphics(Image *newImage, IStream *newStream):
+               Graphics(newImage), image(newImage), stream(newStream)
        {
-               ReleaseDC(hwnd, hdc);
        }
-
 };
 
 static void gdiplusgen_begin_job(GVJ_t *job)
 {
+       UseGdiplus();
        if (!job->external_context)
                job->context = NULL;
+       else if (job->device.id == FORMAT_METAFILE)
+       {
+               /* save the passed-in context in the window field, so we can create a Metafile in the context field later on */
+               job->window = job->context;
+               *((Metafile**)job->window) = NULL;
+               job->context = NULL;
+}
 }
 
 static void gdiplusgen_end_job(GVJ_t *job)
 {
-       if (!job->external_context) {
                Graphics *context = (Graphics *)job->context;
                
+       if (!job->external_context) {
                /* flush and delete the graphics */
                ImageGraphics *imageGraphics = static_cast<ImageGraphics *>(context);
-               GraphicsContext *graphicsContext = imageGraphics->context;
                Image *image = imageGraphics->image;
                IStream *stream = imageGraphics->stream;
                delete imageGraphics;
@@ -101,6 +74,7 @@ static void gdiplusgen_end_job(GVJ_t *job)
                switch (job->device.id) {
                        case FORMAT_EMF:
                        case FORMAT_EMFPLUS:
+                       case FORMAT_METAFILE:
                                break;
                        default:
                                SaveBitmapToStream(*static_cast<Bitmap *>(image), stream, job->device.id);
@@ -117,18 +91,16 @@ static void gdiplusgen_end_job(GVJ_t *job)
                stream->Release();
                gvwrite(job, (unsigned char*)GlobalLock(buffer), GlobalSize(buffer));
                GlobalFree(buffer);
-                       
-               /* since this is an internal job, shut down GDI+ */
-               delete graphicsContext;
        }
+       else if (job->device.id == FORMAT_METAFILE)
+               delete context;
 }
 
 static void gdiplusgen_begin_page(GVJ_t *job)
 {
-       if (!job->external_context && !job->context) {
-               /* since this is an internal job, start up GDI+ */
-               GraphicsContext *context = new GraphicsContext();
-               
+       if (!job->context)
+       {
+               if (!job->external_context) {
                /* allocate memory and attach stream to it */
                HGLOBAL buffer = GlobalAlloc(GMEM_MOVEABLE, 0);
                IStream *stream = NULL;
@@ -144,93 +116,51 @@ static void gdiplusgen_begin_page(GVJ_t *job)
                                DeviceContext().hdc,
                                RectF(0.0f, 0.0f, job->width, job->height),
                                MetafileFrameUnitPixel,
-                               job->device.id == FORMAT_EMFPLUS ? EmfTypeEmfPlusOnly : EmfTypeEmfOnly);
+                                       job->device.id == FORMAT_EMFPLUS ? EmfTypeEmfPlusOnly : EmfTypeEmfPlusDual);
                                /* output in EMF for wider compatibility; output in EMF+ for antialiasing etc. */
                        break;
                        
+                       case FORMAT_METAFILE:
+                               break;
+                               
                default:
                        /* bitmap image */
                        image = new Bitmap (job->width, job->height, PixelFormat32bppARGB);
                        break;
                }
                
-               job->context = new ImageGraphics(context, image, stream);
+                       job->context = new ImageGraphics(image, stream);
+       }
+               else if (job->device.id == FORMAT_METAFILE)
+               {
+                       /* create EMF image in the job window which was set during begin job */
+                       Metafile* metafile = new Metafile(DeviceContext().hdc,
+                               RectF(0.0f, 0.0f, job->width, job->height),
+                               MetafileFrameUnitPixel,
+                               EmfTypeEmfPlusOnly);
+                       *((Metafile**)job->window) = metafile;
+                       job->context = new Graphics(metafile);
+               }
        }
        
        /* start graphics state */
        Graphics *context = (Graphics *)job->context;
        context->SetSmoothingMode(SmoothingModeHighQuality);
+       context->SetTextRenderingHint(TextRenderingHintAntiAlias);
        
        /* set up the context transformation */
-       /* NOTE: we need to shift by height of graph and do a reflection before applying given transformations */
        context->ResetTransform();
-       context->TranslateTransform(0, job->height);
-       context->ScaleTransform(job->scale.x, -job->scale.y);
-       context->RotateTransform(job->rotation);
-       context->TranslateTransform(job->translation.x, job->translation.y);
-}
 
-static int CALLBACK fetch_first_font(
-       const LOGFONTA *logFont,
-       const TEXTMETRICA *textMetrics,
-       DWORD fontType,
-       LPARAM lParam)
-{
-       /* save the first font we see in the font enumeration */
-       *((LOGFONTA *)lParam) = *logFont;
-       return 0;
+       context->ScaleTransform(job->scale.x, job->scale.y);
+       context->RotateTransform(-job->rotation);
+       context->TranslateTransform(job->translation.x, -job->translation.y);
 }
 
-static auto_ptr<Font> find_font(char *fontname, double fontsize)
-{
-       /* search for a font with this name. if we can't find it, use the generic serif instead */
-       /* NOTE: GDI font search is more comprehensive than GDI+ and will look for variants e.g. Arial Bold */
-       DeviceContext reference;
-       LOGFONTA font_to_find;
-       font_to_find.lfCharSet = ANSI_CHARSET;
-       strncpy(font_to_find.lfFaceName, fontname, sizeof(font_to_find.lfFaceName) - 1);
-       font_to_find.lfFaceName[sizeof(font_to_find.lfFaceName) - 1] = '\0';
-       font_to_find.lfPitchAndFamily = 0;
-       LOGFONTA found_font;
-       if (EnumFontFamiliesExA(reference.hdc,
-               &font_to_find,
-               fetch_first_font,
-               (LPARAM)&found_font,
-               0) == 0) {
-               found_font.lfHeight = (LONG)-fontsize;
-               found_font.lfWidth = 0;
-               return auto_ptr<Font>(new Font(reference.hdc, &found_font));
-       }
-       else
-       {
-               strncpy(font_to_find.lfFaceName,"Times New Roman", sizeof(font_to_find.lfFaceName) - 1);
-               font_to_find.lfFaceName[sizeof(font_to_find.lfFaceName) - 1] = '\0';
-               font_to_find.lfPitchAndFamily = 0;
-               if(EnumFontFamiliesExA(reference.hdc,
-                       &font_to_find,
-                               fetch_first_font,
-                               (LPARAM)&found_font,
-               0) == 0) 
-               {
-                       found_font.lfHeight = (LONG)-fontsize;
-                       found_font.lfWidth = 0;
-                       return auto_ptr<Font>(new Font(reference.hdc, &found_font));
-               }
 
-       }
-//     "gdiplus cannot find the default font Times New Roman."
-       return NULL;
-}
 
 static void gdiplusgen_textpara(GVJ_t *job, pointf p, textpara_t *para)
 {
-       /* convert incoming UTF8 string to wide chars */
-       /* NOTE: conversion is 1 or more UTF8 chars to 1 wide char */
-       char* fontname;
-       int wide_count = MultiByteToWideChar(CP_UTF8, 0, para->str, -1, NULL, 0);
-       if (wide_count > 1) {
-               vector<WCHAR> wide_str(wide_count);
-               MultiByteToWideChar(CP_UTF8, 0, para->str, -1, &wide_str.front(), wide_count);
+       Graphics* context = (Graphics*)job->context;
                
                /* adjust text position */
                switch (para->just) {
@@ -245,31 +175,22 @@ static void gdiplusgen_textpara(GVJ_t *job, pointf p, textpara_t *para)
                        p.x -= para->width / 2.0;
                        break;
                }
-               p.y -= para->yoffset_centerline;
-
-               Graphics *context = (Graphics *)job->context;
+       p.y += para->yoffset_centerline + para->yoffset_layout;
 
-               /* reverse the reflection done in begin_page */
-               GraphicsState saved = context->Save();
-               double center = para->fontsize / 2.0;
-               context->TranslateTransform(p.x, p.y + center);
-               context->ScaleTransform(1.0, -1.0);
+       Layout* layout;
+       if (para->free_layout == &gdiplus_free_layout)
+               layout = (Layout*)para->layout;
+       else
+               layout = new Layout(para->fontname, para->fontsize, para->str);
 
-               Gdiplus::Font* a=find_font(para->fontname, para->fontsize).get();
                /* draw the text */
                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]));
+       context->DrawString(&layout->text[0], layout->text.size(), layout->font, PointF(p.x, -p.y), GetGenericTypographic(), &brush);
+       
+       if (para->free_layout != &gdiplus_free_layout)
+               delete layout;
 
-
-#ifdef HAVE_GD_FONTCONFIG
-    if (para->postscript_alias)
-               fontname = para->postscript_alias->family;
-    else
-#endif
-       fontname = para->fontname;
-               context->DrawString(&wide_str.front(), wide_count - 1, find_font(fontname, para->fontsize).get(), PointF(0, -center), &brush);
-               context->Restore(saved);
        }
-}
 
 
 static vector<PointF> points(pointf *A, int n)
@@ -277,7 +198,7 @@ static vector<PointF> points(pointf *A, int n)
        /* convert Graphviz pointf (struct of double) to GDI+ PointF (struct of float) */
        vector<PointF> newPoints;
        for (int i = 0; i < n; ++i)
-               newPoints.push_back(PointF(A[i].x, A[i].y));
+               newPoints.push_back(PointF(A[i].x, -A[i].y));
        return newPoints;
 }
 
@@ -303,7 +224,7 @@ static void gdiplusgen_ellipse(GVJ_t *job, pointf *A, int filled)
        GraphicsPath path;
        double dx = A[1].x - A[0].x;
        double dy = A[1].y - A[0].y;
-       path.AddEllipse(RectF(A[0].x - dx, A[0].y - dy, dx * 2.0, dy * 2.0));
+       path.AddEllipse(RectF(A[0].x - dx, -A[0].y - dy, dx * 2.0, dy * 2.0));
        
        /* draw the path */
        gdiplusgen_path(job, &path, filled);
@@ -375,13 +296,22 @@ static gvrender_engine_t gdiplusgen_engine = {
 };
 
 static gvrender_features_t render_features_gdiplus = {
-       GVRENDER_DOES_TRANSFORM, /* flags */
+       GVRENDER_Y_GOES_DOWN | GVRENDER_DOES_TRANSFORM, /* flags */
     4.,                                                        /* default pad - graph units */
     NULL,                                              /* knowncolors */
     0,                                                 /* sizeof knowncolors */
     RGBA_BYTE                          /* color_type */
 };
 
+static gvdevice_features_t device_features_gdiplus_emf = {
+    GVDEVICE_BINARY_FORMAT
+      | GVDEVICE_DOES_TRUECOLOR
+         | GVRENDER_NO_WHITE_BG,/* flags */
+    {0.,0.},                    /* default margin - points */
+    {0.,0.},                    /* default page width, height - points */
+    {72.,72.}                  /* dpi */
+};
+
 static gvdevice_features_t device_features_gdiplus = {
     GVDEVICE_BINARY_FORMAT
       | GVDEVICE_DOES_TRUECOLOR,/* flags */
@@ -396,9 +326,10 @@ gvplugin_installed_t gvrender_gdiplus_types[] = {
 };
 
 gvplugin_installed_t gvdevice_gdiplus_types[] = {
+       {FORMAT_METAFILE, "metafile:gdiplus", 8, NULL, &device_features_gdiplus_emf},   
        {FORMAT_BMP, "bmp:gdiplus", 8, NULL, &device_features_gdiplus},
-       {FORMAT_EMF, "emf:gdiplus", 8, NULL, &device_features_gdiplus},
-       {FORMAT_EMFPLUS, "emfplus:gdiplus", 8, NULL, &device_features_gdiplus},
+       {FORMAT_EMF, "emf:gdiplus", 8, NULL, &device_features_gdiplus_emf},
+       {FORMAT_EMFPLUS, "emfplus:gdiplus", 8, NULL, &device_features_gdiplus_emf},
        {FORMAT_GIF, "gif:gdiplus", 8, NULL, &device_features_gdiplus},
        {FORMAT_JPEG, "jpe:gdiplus", 8, NULL, &device_features_gdiplus},
        {FORMAT_JPEG, "jpeg:gdiplus", 8, NULL, &device_features_gdiplus},
diff --git a/plugin/gdiplus/gvtextlayout_gdiplus.cpp b/plugin/gdiplus/gvtextlayout_gdiplus.cpp
new file mode 100755 (executable)
index 0000000..2e445e1
--- /dev/null
@@ -0,0 +1,124 @@
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+*      This software is part of the graphviz package      *
+*                http://www.graphviz.org/                 *
+*                                                         *
+*            Copyright (c) 1994-2008 AT&T Corp.           *
+*                and is licensed under the                *
+*            Common Public License, Version 1.0           *
+*                      by AT&T Corp.                      *
+*                                                         *
+*        Information and Software Systems Research        *
+*              AT&T Research, Florham Park NJ             *
+**********************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "gvplugin_textlayout.h"
+#include "gvplugin_gdiplus.h"
+
+static int count = 0;
+
+using namespace Gdiplus;
+
+static int CALLBACK fetch_first_font(
+       const LOGFONTA *logFont,
+       const TEXTMETRICA *textMetrics,
+       DWORD fontType,
+       LPARAM lParam)
+{
+       /* save the first font we see in the font enumeration */
+       *((LOGFONTA *)lParam) = *logFont;
+       return 0;
+}
+
+Layout::Layout(char *fontname, double fontsize, char* string)
+{
+       /* convert incoming UTF8 string to wide chars */
+       /* NOTE: conversion is 1 or more UTF8 chars to 1 wide char */
+       int len = strlen(string);
+       text.resize(len);
+       text.resize(MultiByteToWideChar(CP_UTF8, 0, string, len, &text[0], len));
+
+       /* search for a font with this name. if we can't find it, use the generic serif instead */
+       /* NOTE: GDI font search is more comprehensive than GDI+ and will look for variants e.g. Arial Bold */
+       DeviceContext reference;
+       LOGFONTA font_to_find;
+       font_to_find.lfCharSet = ANSI_CHARSET;
+       strncpy(font_to_find.lfFaceName, fontname, sizeof(font_to_find.lfFaceName) - 1);
+       font_to_find.lfFaceName[sizeof(font_to_find.lfFaceName) - 1] = '\0';
+       font_to_find.lfPitchAndFamily = 0;
+       LOGFONTA found_font;
+       if (EnumFontFamiliesExA(reference.hdc,
+               &font_to_find,
+               fetch_first_font,
+               (LPARAM)&found_font,
+               0) == 0) {
+               found_font.lfHeight = (LONG)-fontsize;
+               found_font.lfWidth = 0;
+               font = new Font(reference.hdc, &found_font);
+       }
+       else
+               font = new Font(FontFamily::GenericSerif(), fontsize);
+}
+
+Layout::~Layout()
+{
+       delete font;
+}
+
+void gdiplus_free_layout(void *layout)
+{
+       if (layout)
+               delete (Layout*)layout;
+};
+
+boolean gdiplus_textlayout(textpara_t *para, char **fontpath)
+{
+       /* ensure GDI+ is started up: since we get called outside of a job, we can't rely on GDI+ startup then */
+       UseGdiplus();
+       
+       Layout* layout = new Layout(para->fontname, para->fontsize, para->str);
+       
+       /* measure the text */
+       /* NOTE: use TextRenderingHintAntiAlias + GetGenericTypographic to get a layout without extra space at beginning and end */
+       RectF boundingBox;
+       DeviceContext deviceContext;
+       Graphics measureGraphics(deviceContext.hdc);
+       measureGraphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
+       measureGraphics.MeasureString(
+               &layout->text[0],
+               layout->text.size(),
+               layout->font,
+               PointF(0.0f, 0.0f),
+               GetGenericTypographic(),
+               &boundingBox);
+               
+       FontFamily fontFamily;
+       layout->font->GetFamily(&fontFamily);
+       int style = layout->font->GetStyle();
+               
+       para->layout = (void*)layout;
+       para->free_layout = &gdiplus_free_layout;
+       para->width = boundingBox.Width;
+       para->height = layout->font->GetHeight(&measureGraphics);
+       para->yoffset_layout = fontFamily.GetCellAscent(style) * para->fontsize / fontFamily.GetEmHeight(style); /* convert design units to pixels */
+       para->yoffset_centerline = 0;
+       return TRUE;
+};
+
+static gvtextlayout_engine_t gdiplus_textlayout_engine = {
+    gdiplus_textlayout
+};
+
+gvplugin_installed_t gvtextlayout_gdiplus_types[] = {
+    {0, "textlayout", 8, &gdiplus_textlayout_engine, NULL},
+    {0, NULL, 0, NULL, NULL}
+};