123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #include "config.h"
- #include <cassert>
- #include <climits>
- #include <stdlib.h>
- #include <string.h>
- #include <gvc/gvplugin_device.h>
- #include <gvc/gvplugin_render.h>
- #include <gvc/gvio.h>
- #include "gvplugin_gdiplus.h"
- #include <memory>
- #include <vector>
- using namespace std;
- using namespace Gdiplus;
- /* Graphics for internal use, so that we can record image etc. for subsequent retrieval off the job struct */
- struct ImageGraphics: public Graphics
- {
- Image *image;
- IStream *stream;
- ImageGraphics(Image *newImage, IStream *newStream):
- Graphics(newImage), image(newImage), stream(newStream)
- {
- }
- };
- static void gdiplusgen_begin_job(GVJ_t *job)
- {
- UseGdiplus();
- if (!job->external_context)
- job->context = nullptr;
- 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;
- auto m = reinterpret_cast<Metafile**>(job->window);
- *m = nullptr;
- job->context = nullptr;
- }
- }
- static void gdiplusgen_end_job(GVJ_t *job)
- {
- auto context = reinterpret_cast<Graphics*>(job->context);
- if (!job->external_context) {
- /* flush and delete the graphics */
- ImageGraphics *imageGraphics = static_cast<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);
- break;
- }
- delete image; /* NOTE: in the case of EMF, this actually flushes out the image to the underlying stream */
- /* blast the streamed buffer back to the gvdevice */
- /* NOTE: this is somewhat inefficient since we should be streaming directly to gvdevice rather than buffering first */
- /* ... however, GDI+ requires any such direct IStream to implement Seek Read, Write, Stat methods and gvdevice really only offers a write-once model */
- HGLOBAL buffer = nullptr;
- GetHGlobalFromStream(stream, &buffer);
- stream->Release();
- gvwrite(job, (char*)GlobalLock(buffer), GlobalSize(buffer));
- GlobalFree(buffer);
- }
- else if (job->device.id == FORMAT_METAFILE)
- delete context;
- }
- static void gdiplusgen_begin_page(GVJ_t *job)
- {
- if (!job->context)
- {
- if (!job->external_context && job->device.id != FORMAT_METAFILE) {
- /* allocate memory and attach stream to it */
- HGLOBAL buffer = GlobalAlloc(GMEM_MOVEABLE, 0);
- IStream *stream = nullptr;
- CreateStreamOnHGlobal(buffer, FALSE, &stream); /* FALSE means don't deallocate buffer when releasing stream */
- Image *image;
- switch (job->device.id) {
- case FORMAT_EMF:
- case FORMAT_EMFPLUS:
- /* EMF image */
- image = new Metafile (stream,
- DeviceContext().hdc,
- RectF(0.0f, 0.0f, job->width, job->height),
- MetafileFrameUnitPixel,
- job->device.id == FORMAT_EMFPLUS ? EmfTypeEmfPlusOnly : EmfTypeEmfPlusDual);
- /* output in EMF for wider compatibility; output in EMF+ for antialiasing etc. */
- break;
- default:
- /* bitmap image */
- image = new Bitmap (job->width, job->height, PixelFormat32bppARGB);
- break;
- }
- 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);
- auto m = reinterpret_cast<Metafile**>(job->window);
- *m = 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 */
- context->ResetTransform();
- context->ScaleTransform(job->scale.x, job->scale.y);
- context->RotateTransform(-job->rotation);
- context->TranslateTransform(job->translation.x, -job->translation.y);
- }
- static void gdiplusgen_textspan(GVJ_t *job, pointf p, textspan_t *span)
- {
- auto context = reinterpret_cast<Graphics*>(job->context);
- /* adjust text position */
- switch (span->just) {
- case 'r':
- p.x -= span->size.x;
- break;
- case 'l':
- p.x -= 0.0;
- break;
- case 'n':
- default:
- p.x -= span->size.x / 2.0;
- break;
- }
- p.y += span->yoffset_centerline + span->yoffset_layout;
- Layout* layout;
- if (span->free_layout == &gdiplus_free_layout)
- layout = reinterpret_cast<Layout*>(span->layout);
- else
- layout = new Layout(span->font->name, span->font->size, span->str);
- /* 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.get(), PointF(p.x, -p.y), GetGenericTypographic(), &brush);
- if (span->free_layout != &gdiplus_free_layout)
- delete layout;
- }
- 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));
- return newPoints;
- }
- static void gdiplusgen_path(GVJ_t *job, const GraphicsPath &pathname,
- int filled) {
- auto context = reinterpret_cast<Graphics *>(job->context);
- /* fill the given path with job fill color */
- if (filled) {
- 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]));
- context->FillPath(&fill_brush, &pathname);
- }
- /* draw the given path from job pen color and pen width */
- 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]),
- job->obj->penwidth);
- /*
- * Set line type
- * See http://msdn.microsoft.com/en-us/library/ms535050%28v=vs.85%29.aspx
- */
- switch (job->obj->pen) {
- case PEN_NONE:
- return;
- case PEN_DASHED:
- draw_pen.SetDashStyle(DashStyleDash);
- break;
- case PEN_DOTTED:
- draw_pen.SetDashStyle(DashStyleDot);
- break;
- case PEN_SOLID:
- break;
- }
- context->DrawPath(&draw_pen, &pathname);
- }
- static void gdiplusgen_ellipse(GVJ_t *job, pointf *A, int filled)
- {
- /* convert ellipse into path */
- GraphicsPath pathname;
- double dx = A[1].x - A[0].x;
- double dy = A[1].y - A[0].y;
- pathname.AddEllipse(RectF(A[0].x - dx, -A[0].y - dy, dx * 2.0, dy * 2.0));
- /* draw the path */
- gdiplusgen_path(job, pathname, filled);
- }
- static void gdiplusgen_polygon(GVJ_t *job, pointf *A, size_t n, int filled) {
- /* convert polygon into path */
- GraphicsPath pathname;
- assert(n <= INT_MAX);
- pathname.AddPolygon(&points(A, n).front(), (int)n);
- /* draw the path */
- gdiplusgen_path(job, pathname, filled);
- }
- static void gdiplusgen_bezier(GVJ_t *job, pointf *A, size_t n, int filled) {
- /* convert the beziers into path */
- GraphicsPath pathname;
- assert(n <= INT_MAX);
- pathname.AddBeziers(&points(A, n).front(), (int)n);
- /* draw the path */
- gdiplusgen_path(job, pathname, filled);
- }
- static void gdiplusgen_polyline(GVJ_t *job, pointf *A, size_t n) {
- /* convert the lines into path */
- GraphicsPath pathname;
- assert(n <= INT_MAX);
- pathname.AddLines(&points(A,n).front(), (int)n);
- /* draw the path */
- gdiplusgen_path(job, pathname, 0);
- }
- static gvrender_engine_t gdiplusgen_engine = {
- gdiplusgen_begin_job,
- gdiplusgen_end_job,
- 0, /* gdiplusgen_begin_graph */
- 0, /* gdiplusgen_end_graph */
- 0, /* gdiplusgen_begin_layer */
- 0, /* gdiplusgen_end_layer */
- gdiplusgen_begin_page,
- 0, /* gdiplusgen_end_page */
- 0, /* gdiplusgen_begin_cluster */
- 0, /* gdiplusgen_end_cluster */
- 0, /* gdiplusgen_begin_nodes */
- 0, /* gdiplusgen_end_nodes */
- 0, /* gdiplusgen_begin_edges */
- 0, /* gdiplusgen_end_edges */
- 0, /* gdiplusgen_begin_node */
- 0, /* gdiplusgen_end_node */
- 0, /* gdiplusgen_begin_edge */
- 0, /* gdiplusgen_end_edge */
- 0, /* gdiplusgen_begin_anchor */
- 0, /* gdiplusgen_end_anchor */
- 0, /* gdiplusgen_begin_label */
- 0, /* gdiplusgen_end_label */
- gdiplusgen_textspan,
- 0,
- gdiplusgen_ellipse,
- gdiplusgen_polygon,
- gdiplusgen_bezier,
- gdiplusgen_polyline,
- 0, /* gdiplusgen_comment */
- 0, /* gdiplusgen_library_shape */
- };
- static gvrender_features_t render_features_gdiplus = {
- GVRENDER_Y_GOES_DOWN | GVRENDER_DOES_TRANSFORM, /* flags */
- 4., /* default pad - graph units */
- nullptr, /* 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 */
- {0.,0.}, /* default margin - points */
- {0.,0.}, /* default page width, height - points */
- {96.,96.} /* dpi */
- };
- gvplugin_installed_t gvrender_gdiplus_types[] = {
- {0, "gdiplus", 1, &gdiplusgen_engine, &render_features_gdiplus},
- {0, nullptr, 0, nullptr, nullptr}
- };
- gvplugin_installed_t gvdevice_gdiplus_types[] = {
- {FORMAT_METAFILE, "metafile:gdiplus", 8, nullptr, &device_features_gdiplus_emf},
- {FORMAT_BMP, "bmp:gdiplus", 8, nullptr, &device_features_gdiplus},
- {FORMAT_EMF, "emf:gdiplus", 8, nullptr, &device_features_gdiplus_emf},
- {FORMAT_EMFPLUS, "emfplus:gdiplus", 8, nullptr, &device_features_gdiplus_emf},
- {FORMAT_GIF, "gif:gdiplus", 8, nullptr, &device_features_gdiplus},
- {FORMAT_JPEG, "jpe:gdiplus", 8, nullptr, &device_features_gdiplus},
- {FORMAT_JPEG, "jpeg:gdiplus", 8, nullptr, &device_features_gdiplus},
- {FORMAT_JPEG, "jpg:gdiplus", 8, nullptr, &device_features_gdiplus},
- {FORMAT_PNG, "png:gdiplus", 8, nullptr, &device_features_gdiplus},
- {FORMAT_TIFF, "tif:gdiplus", 8, nullptr, &device_features_gdiplus},
- {FORMAT_TIFF, "tiff:gdiplus", 8, nullptr, &device_features_gdiplus},
- {0, nullptr, 0, nullptr, nullptr}
- };
|