#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[];
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,
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 */
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 };
#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
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;
switch (job->device.id) {
case FORMAT_EMF:
case FORMAT_EMFPLUS:
+ case FORMAT_METAFILE:
break;
default:
SaveBitmapToStream(*static_cast<Bitmap *>(image), stream, job->device.id);
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;
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) {
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)
/* 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;
}
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);
};
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 */
};
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},
--- /dev/null
+/* $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}
+};