From: glenlow Date: Mon, 25 May 2009 06:52:18 +0000 (+0000) Subject: GDI+ textlayout; startup and shutdown GDI+ correctly when used outside of plugin... X-Git-Tag: LAST_LIBGRAPH~32^2~2029 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=eff48627602c397f6dcab79627bdfa01a733eb5d;p=graphviz GDI+ textlayout; startup and shutdown GDI+ correctly when used outside of plugin; GDI+ actually uses GVRENDER_Y_GOES_DOWN --- diff --git a/plugin/gdiplus/Makefile.am b/plugin/gdiplus/Makefile.am index 32496e3cc..8892a7ee4 100644 --- a/plugin/gdiplus/Makefile.am +++ b/plugin/gdiplus/Makefile.am @@ -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" diff --git a/plugin/gdiplus/gvdevice_gdiplus.cpp b/plugin/gdiplus/gvdevice_gdiplus.cpp index 396d8bda8..5ca93560b 100755 --- a/plugin/gdiplus/gvdevice_gdiplus.cpp +++ b/plugin/gdiplus/gvdevice_gdiplus.cpp @@ -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 */ diff --git a/plugin/gdiplus/gvplugin_gdiplus.cpp b/plugin/gdiplus/gvplugin_gdiplus.cpp index e6adc1ca0..a36aec144 100755 --- a/plugin/gdiplus/gvplugin_gdiplus.cpp +++ b/plugin/gdiplus/gvplugin_gdiplus.cpp @@ -18,10 +18,8 @@ #include "gvplugin_gdiplus.h" -#include - 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 diff --git a/plugin/gdiplus/gvplugin_gdiplus.h b/plugin/gdiplus/gvplugin_gdiplus.h index 5cbf359d8..f86071687 100755 --- a/plugin/gdiplus/gvplugin_gdiplus.h +++ b/plugin/gdiplus/gvplugin_gdiplus.h @@ -17,11 +17,14 @@ #ifndef GVPLUGIN_GDIPLUS_H #define GVPLUGIN_GDIPLUS_H +#include + #include #include 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 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 diff --git a/plugin/gdiplus/gvrender_gdiplus.cpp b/plugin/gdiplus/gvrender_gdiplus.cpp index a2b486c9a..13c4b4cde 100755 --- a/plugin/gdiplus/gvrender_gdiplus.cpp +++ b/plugin/gdiplus/gvrender_gdiplus.cpp @@ -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(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(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 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(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(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 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 points(pointf *A, int n) @@ -277,7 +198,7 @@ static vector points(pointf *A, int n) /* convert Graphviz pointf (struct of double) to GDI+ PointF (struct of float) */ vector 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 index 000000000..2e445e144 --- /dev/null +++ b/plugin/gdiplus/gvtextlayout_gdiplus.cpp @@ -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 +#include + +#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} +};