1 /*
2 Copyright (c) 2011-2017 Bastian Maerkisch. All rights reserved.
3 
4 Redistribution and use in source and binary forms, with or without modification,
5 are permitted provided that the following conditions are met:
6 
7     1. Redistributions of source code must retain the above copyright notice,
8     this list of conditions and the following disclaimer.
9 
10     2. Redistributions in binary form must reproduce the above copyright notice,
11     this list of conditions and the following disclaimer in the documentation
12     and/or other materials provided with the distribution.
13 
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25 
26 // include iostream / cstdio _before_ syscfg.h in order
27 // to avoid re-definition by wtext.h/winmain.c routines
28 #include <iostream>
29 #include <vector>
30 extern "C" {
31 # include "syscfg.h"
32 }
33 #include <windows.h>
34 #include <windowsx.h>
35 #define GDIPVER 0x0110
36 #include <gdiplus.h>
37 #include <tchar.h>
38 #include <wchar.h>
39 #ifdef __WATCOMC__
40 // swprintf_s is missing from <cwchar>
41 # define swprintf_s(s, c, f, ...) swprintf(s, c, f, __VA_ARGS__)
42 #endif
43 
44 #include "wgdiplus.h"
45 #include "wgnuplib.h"
46 #include "winmain.h"
47 #include "wcommon.h"
48 using namespace Gdiplus;
49 // do not use namespace std: otherwise MSVC complains about
50 // ambiguous symbol bool
51 //using namespace std;
52 
53 static bool gdiplusInitialized = false;
54 static ULONG_PTR gdiplusToken;
55 
56 #define MINMAX(a,val,b) (((val) <= (a)) ? (a) : ((val) <= (b) ? (val) : (b)))
57 const int pattern_num = 8;
58 
59 enum draw_target { DRAW_SCREEN, DRAW_PRINTER, DRAW_PLOTTER, DRAW_METAFILE };
60 
61 static Color gdiplusCreateColor(COLORREF color, double alpha);
62 static void gdiplusSetDashStyle(Pen *pen, enum DashStyle style);
63 static void gdiplusPolyline(Graphics &graphics, Pen &pen, Point *points, int polyi);
64 Brush * gdiplusPatternBrush(int style, COLORREF color, double alpha, COLORREF backcolor, BOOL transparent);
65 static void gdiplusDot(Graphics &graphics, Brush &brush, int x, int y);
66 static Font * SetFont_gdiplus(Graphics &graphics, LPRECT rect, LPGW lpgw, LPTSTR fontname, int size);
67 static void do_draw_gdiplus(LPGW lpgw, Graphics &graphics, LPRECT rect, enum draw_target target);
68 
69 
70 /* Internal state of enhanced text processing.
71 */
72 
73 static struct {
74 	Graphics * graphics; /* graphics object */
75 	Font * font;
76 	SolidBrush * brush;
77 	StringFormat *stringformat;
78 } enhstate_gdiplus;
79 
80 
81 /* ****************  ....   ************************* */
82 
83 
84 void
gdiplusInit(void)85 gdiplusInit(void)
86 {
87 	if (!gdiplusInitialized) {
88 		gdiplusInitialized = true;
89 		GdiplusStartupInput gdiplusStartupInput;
90 		GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
91 	}
92 }
93 
94 
95 void
gdiplusCleanup(void)96 gdiplusCleanup(void)
97 {
98 	if (gdiplusInitialized) {
99 		gdiplusInitialized = false;
100 		GdiplusShutdown(gdiplusToken);
101 	}
102 }
103 
104 
105 static Color
gdiplusCreateColor(COLORREF color,double alpha)106 gdiplusCreateColor(COLORREF color, double alpha)
107 {
108 	ARGB argb = Color::MakeARGB(
109 		BYTE(255 * alpha),
110 		GetRValue(color), GetGValue(color), GetBValue(color));
111 	return Color(argb);
112 }
113 
114 
115 static void
gdiplusSetDashStyle(Pen * pen,enum DashStyle style)116 gdiplusSetDashStyle(Pen *pen, enum DashStyle style)
117 {
118 	const REAL dashstyles[4][6] = {
119 		{ 16.f, 8.f },	// dash
120 		{ 3.f, 3.f },	// dot
121 		{ 8.f, 5.f, 3.f, 5.f }, // dash dot
122 		{ 8.f, 4.f, 3.f, 4.f, 3.f, 4.f } // dash dot dot
123 	};
124 	const int dashstyle_len[4] = { 2, 2, 4, 6 };
125 
126 	style = static_cast<enum DashStyle>(style % 5);
127 	if (style == 0)
128 		pen->SetDashStyle(style);
129 	else
130 		pen->SetDashPattern(dashstyles[style - 1], dashstyle_len[style - 1]);
131 }
132 
133 
134 /* ****************  GDI+ only functions ********************************** */
135 
136 
137 static void
gdiplusPolyline(Graphics & graphics,Pen & pen,Point * points,int polyi)138 gdiplusPolyline(Graphics &graphics, Pen &pen, Point *points, int polyi)
139 {
140 	// Dash patterns get scaled with line width, in contrast to GDI.
141 	// Avoid smearing out caused by antialiasing for small line widths.
142 	SmoothingMode mode = graphics.GetSmoothingMode();
143 
144 	if ((points[0].X != points[polyi - 1].X) ||
145 		(points[0].Y != points[polyi - 1].Y))
146 		graphics.DrawLines(&pen, points, polyi);
147 	else
148 		graphics.DrawPolygon(&pen, points, polyi - 1);
149 
150 	/* restore */
151 	if (mode != SmoothingModeNone)
152 		graphics.SetSmoothingMode(mode);
153 }
154 
155 
156 static void
gdiplusPolyline(Graphics & graphics,Pen & pen,PointF * points,int polyi)157 gdiplusPolyline(Graphics &graphics, Pen &pen, PointF *points, int polyi)
158 {
159 	// Dash patterns get scaled with line width, in contrast to GDI.
160 	// Avoid smearing out caused by antialiasing for small line widths.
161 	SmoothingMode mode = graphics.GetSmoothingMode();
162 
163 	bool all_vert_or_horz = true;
164 	for (int i = 1; i < polyi; i++)
165 		if (!((points[i - 1].X == points[i].X) ||
166 		      (points[i - 1].Y == points[i].Y)))
167 			all_vert_or_horz = false;
168 
169 	// if all lines are horizontal or vertical we snap to nearest pixel
170 	// to avoid "blurry" lines
171 	if (all_vert_or_horz) {
172 		for (int i = 0; i < polyi; i++) {
173 			points[i].X = INT(points[i].X + 0.5);
174 			points[i].Y = INT(points[i].Y + 0.5);
175 		}
176 	}
177 
178 	if ((points[0].X != points[polyi - 1].X) ||
179 	    (points[0].Y != points[polyi - 1].Y))
180 		graphics.DrawLines(&pen, points, polyi);
181 	else
182 		graphics.DrawPolygon(&pen, points, polyi - 1);
183 
184 	/* restore */
185 	if (mode != SmoothingModeNone)
186 		graphics.SetSmoothingMode(mode);
187 }
188 
189 
190 Brush *
gdiplusPatternBrush(int style,COLORREF color,double alpha,COLORREF backcolor,BOOL transparent)191 gdiplusPatternBrush(int style, COLORREF color, double alpha, COLORREF backcolor, BOOL transparent)
192 {
193 	Color gdipColor = gdiplusCreateColor(color, alpha);
194 	Color gdipBackColor = gdiplusCreateColor(backcolor, transparent ? 0 : 1.);
195 	Brush * brush;
196 	style %= pattern_num;
197 	const HatchStyle styles[] = { HatchStyleTotal, HatchStyleDiagonalCross,
198 		HatchStyleZigZag, HatchStyleTotal,
199 		HatchStyleForwardDiagonal, HatchStyleBackwardDiagonal,
200 		HatchStyleLightDownwardDiagonal, HatchStyleDarkUpwardDiagonal };
201 	switch (style) {
202 		case 0:
203 			brush = new SolidBrush(gdipBackColor);
204 			break;
205 		case 3:
206 			brush = new SolidBrush(gdipColor);
207 			break;
208 		default:
209 			brush = new HatchBrush(styles[style], gdipColor, gdipBackColor);
210 	}
211 	return brush;
212 }
213 
214 
215 static void
gdiplusDot(Graphics & graphics,Brush & brush,int x,int y)216 gdiplusDot(Graphics &graphics, Brush &brush, int x, int y)
217 {
218 	/* no antialiasing in order to avoid blurred pixel */
219 	SmoothingMode mode = graphics.GetSmoothingMode();
220 	graphics.SetSmoothingMode(SmoothingModeNone);
221 	graphics.FillRectangle(&brush, x, y, 1, 1);
222 	graphics.SetSmoothingMode(mode);
223 }
224 
225 
226 static Font *
SetFont_gdiplus(Graphics & graphics,LPRECT rect,LPGW lpgw,LPTSTR fontname,int size)227 SetFont_gdiplus(Graphics &graphics, LPRECT rect, LPGW lpgw, LPTSTR fontname, int size)
228 {
229 	if ((fontname == NULL) || (*fontname == 0))
230 		fontname = lpgw->deffontname;
231 	if (size == 0)
232 		size = lpgw->deffontsize;
233 
234 	/* make a local copy */
235 	fontname = _tcsdup(fontname);
236 
237 	/* save current font */
238 	_tcscpy(lpgw->fontname, fontname);
239 	lpgw->fontsize = size;
240 
241 	// apply fontscale
242 	size *= lpgw->fontscale;
243 
244 	/* set up font style */
245 	INT fontStyle = FontStyleRegular;
246 	LPTSTR italic, bold, underline, strikeout;
247 	if ((italic = _tcsstr(fontname, TEXT(" Italic"))) != NULL)
248 		fontStyle |= FontStyleItalic;
249 	else if ((italic = _tcsstr(fontname, TEXT(":Italic"))) != NULL)
250 		fontStyle |= FontStyleItalic;
251 	if ((bold = _tcsstr(fontname, TEXT(" Bold"))) != NULL)
252 		fontStyle |= FontStyleBold;
253 	else if ((bold = _tcsstr(fontname, TEXT(":Bold"))) != NULL)
254 		fontStyle |= FontStyleBold;
255 	if ((underline = _tcsstr(fontname, TEXT(" Underline"))) != NULL)
256 		fontStyle |= FontStyleUnderline;
257 	if ((strikeout = _tcsstr(fontname, TEXT(" Strikeout"))) != NULL)
258 		fontStyle |= FontStyleStrikeout;
259 	if (italic) *italic = 0;
260 	if (bold) *bold = 0;
261 	if (underline) *underline = 0;
262 	if (strikeout) *strikeout = 0;
263 
264 #ifdef UNICODE
265 	const FontFamily * fontFamily = new FontFamily(fontname);
266 #else
267 	LPWSTR family = UnicodeText(fontname, lpgw->encoding);
268 	const FontFamily * fontFamily = new FontFamily(family);
269 	free(family);
270 #endif
271 	free(fontname);
272 	Font * font;
273 	int fontHeight;
274 	bool deleteFontFamily = true;
275 	if (fontFamily->GetLastStatus() != Ok) {
276 		delete fontFamily;
277 #if (!defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR))
278 		// MinGW 4.8.1 does not have this
279 		fontFamily = FontFamily::GenericSansSerif();
280 		deleteFontFamily = false;
281 #else
282 #ifdef UNICODE
283 		fontFamily = new FontFamily(GraphDefaultFont());
284 #else
285 		family = UnicodeText(GraphDefaultFont(), S_ENC_DEFAULT); // should always be available
286 		fontFamily = new FontFamily(family);
287 		free(family);
288 #endif
289 #endif
290 	}
291 	font = new Font(fontFamily, size, fontStyle, UnitPoint);
292 	double scale = font->GetSize() / fontFamily->GetEmHeight(fontStyle) * graphics.GetDpiY() / 72.;
293 	/* store text metrics for later use */
294 	lpgw->tmHeight = fontHeight = scale * (fontFamily->GetCellAscent(fontStyle) + fontFamily->GetCellDescent(fontStyle));
295 	lpgw->tmAscent = scale * fontFamily->GetCellAscent(fontStyle);
296 	lpgw->tmDescent = scale * fontFamily->GetCellDescent(fontStyle);
297 	if (deleteFontFamily)
298 		delete fontFamily;
299 
300 	RectF box;
301 	graphics.MeasureString(L"0123456789", -1, font, PointF(0, 0), StringFormat::GenericTypographic(), &box);
302 	lpgw->vchar = MulDiv(fontHeight, lpgw->ymax, rect->bottom - rect->top);
303 	lpgw->hchar = MulDiv(box.Width, lpgw->xmax, 10 * (rect->right - rect->left));
304 	lpgw->htic = MulDiv(lpgw->hchar, 2, 5);
305 	unsigned cy = MulDiv(box.Width, 2 * graphics.GetDpiY(), 50 * graphics.GetDpiX());
306 	lpgw->vtic = MulDiv(cy, lpgw->ymax, rect->bottom - rect->top);
307 
308 	// Can always rotate text.
309 	lpgw->rotate = TRUE;
310 
311 	return font;
312 }
313 
314 
315 void
InitFont_gdiplus(LPGW lpgw,HDC hdc,LPRECT rect)316 InitFont_gdiplus(LPGW lpgw, HDC hdc, LPRECT rect)
317 {
318 	gdiplusInit();
319 	Graphics graphics(hdc);
320 	// call for the side effects:  set vchar/hchar and text metrics
321 	Font * font = SetFont_gdiplus(graphics, rect, lpgw, lpgw->fontname, lpgw->fontsize);
322 	// TODO:  save font object for later use
323 	delete font;
324 }
325 
326 
327 static void
EnhancedSetFont()328 EnhancedSetFont()
329 {
330 	if ((enhstate.lpgw->fontsize != enhstate.fontsize) ||
331 	    (_tcscmp(enhstate.lpgw->fontname, enhstate.fontname) != 0)) {
332 		if (enhstate_gdiplus.font)
333 			delete enhstate_gdiplus.font;
334 		enhstate_gdiplus.font = SetFont_gdiplus(*enhstate_gdiplus.graphics, enhstate.rect, enhstate.lpgw, enhstate.fontname, enhstate.fontsize);
335 	}
336 }
337 
338 
339 static unsigned
EnhancedTextLength(char * text)340 EnhancedTextLength(char * text)
341 {
342 	LPWSTR textw = UnicodeTextWithEscapes(enhanced_text, enhstate.lpgw->encoding);
343 	RectF box;
344 	enhstate_gdiplus.graphics->MeasureString(textw, -1, enhstate_gdiplus.font, PointF(0, 0), enhstate_gdiplus.stringformat, &box);
345 	free(textw);
346 	return ceil(box.Width);
347 }
348 
349 
350 static void
EnhancedPutText(int x,int y,char * text)351 EnhancedPutText(int x, int y, char * text)
352 {
353 	LPWSTR textw = UnicodeTextWithEscapes(text, enhstate.lpgw->encoding);
354 	Graphics *g = enhstate_gdiplus.graphics;
355 	if (enhstate.lpgw->angle == 0) {
356 		PointF pointF(x, y + enhstate.lpgw->tmDescent);
357 		g->DrawString(textw, -1, enhstate_gdiplus.font, pointF, enhstate_gdiplus.stringformat, enhstate_gdiplus.brush);
358 	} else {
359 		/* shift rotated text correctly */
360 		g->TranslateTransform(x, y);
361 		g->RotateTransform(-enhstate.lpgw->angle);
362 		g->DrawString(textw, -1, enhstate_gdiplus.font, PointF(0, enhstate.lpgw->tmDescent), enhstate_gdiplus.stringformat, enhstate_gdiplus.brush);
363 		g->ResetTransform();
364 	}
365 	free(textw);
366 }
367 
368 
369 static void
EnhancedCleanup()370 EnhancedCleanup()
371 {
372 	delete enhstate_gdiplus.font;
373 	delete enhstate_gdiplus.stringformat;
374 }
375 
376 
377 static void
draw_enhanced_init(LPGW lpgw,Graphics & graphics,SolidBrush & brush,LPRECT rect)378 draw_enhanced_init(LPGW lpgw, Graphics &graphics, SolidBrush &brush, LPRECT rect)
379 {
380 	enhstate.set_font = &EnhancedSetFont;
381 	enhstate.text_length = &EnhancedTextLength;
382 	enhstate.put_text = &EnhancedPutText;
383 	enhstate.cleanup = &EnhancedCleanup;
384 
385 	enhstate_gdiplus.graphics = &graphics;
386 	enhstate_gdiplus.font = SetFont_gdiplus(graphics, rect, lpgw, lpgw->fontname, lpgw->fontsize);
387 	enhstate_gdiplus.brush = &brush;
388 	enhstate.res_scale = graphics.GetDpiY() / 96.;
389 
390 	enhstate_gdiplus.stringformat = new StringFormat(StringFormat::GenericTypographic());
391 	enhstate_gdiplus.stringformat->SetAlignment(StringAlignmentNear);
392 	enhstate_gdiplus.stringformat->SetLineAlignment(StringAlignmentFar);
393 	INT flags = enhstate_gdiplus.stringformat->GetFormatFlags();
394 	flags |= StringFormatFlagsMeasureTrailingSpaces;
395 	enhstate_gdiplus.stringformat->SetFormatFlags(flags);
396 }
397 
398 
399 void
drawgraph_gdiplus(LPGW lpgw,HDC hdc,LPRECT rect)400 drawgraph_gdiplus(LPGW lpgw, HDC hdc, LPRECT rect)
401 {
402 	gdiplusInit();
403 	Graphics graphics(hdc);
404 	do_draw_gdiplus(lpgw, graphics, rect, DRAW_SCREEN);
405 }
406 
407 
408 void
metafile_gdiplus(LPGW lpgw,HDC hdc,LPRECT lprect,LPWSTR name)409 metafile_gdiplus(LPGW lpgw, HDC hdc, LPRECT lprect, LPWSTR name)
410 {
411 	gdiplusInit();
412 	Rect rect(lprect->left, lprect->top, lprect->right - lprect->left, lprect->bottom - lprect->top);
413 	Metafile metafile(name, hdc, rect, MetafileFrameUnitPixel, EmfTypeEmfPlusDual, NULL);
414 	Graphics graphics(&metafile);
415 	do_draw_gdiplus(lpgw, graphics, lprect, DRAW_METAFILE);
416 }
417 
418 
419 HENHMETAFILE
clipboard_gdiplus(LPGW lpgw,HDC hdc,LPRECT lprect)420 clipboard_gdiplus(LPGW lpgw, HDC hdc, LPRECT lprect)
421 {
422 	gdiplusInit();
423 	Rect rect(lprect->left, lprect->top, lprect->right - lprect->left, lprect->bottom - lprect->top);
424 	Metafile metafile(hdc, rect, MetafileFrameUnitPixel, EmfTypeEmfPlusDual, NULL);
425 	// Note: We can only get a valid handle once the graphics object has released
426 	// the metafile. Creating the graphics object on the heap seems the only way to
427 	// achieve that.
428 	Graphics * graphics = Graphics::FromImage(&metafile);
429 	do_draw_gdiplus(lpgw, *graphics, lprect, DRAW_METAFILE);
430 	delete graphics;
431 	return metafile.GetHENHMETAFILE();
432 }
433 
434 
435 void
print_gdiplus(LPGW lpgw,HDC hdc,HANDLE printer,LPRECT rect)436 print_gdiplus(LPGW lpgw, HDC hdc, HANDLE printer, LPRECT rect)
437 {
438 	gdiplusInit();
439 
440 	// temporarily turn of antialiasing
441 	BOOL aa = lpgw->antialiasing;
442 	lpgw->antialiasing = FALSE;
443 
444 	Graphics graphics(hdc, printer);
445 	graphics.SetPageUnit(UnitPixel);
446 	do_draw_gdiplus(lpgw, graphics, rect, DRAW_PRINTER);
447 
448 	// restore settings
449 	lpgw->antialiasing = aa;
450 }
451 
452 
453 static void
do_draw_gdiplus(LPGW lpgw,Graphics & graphics,LPRECT rect,enum draw_target target)454 do_draw_gdiplus(LPGW lpgw, Graphics &graphics, LPRECT rect, enum draw_target target)
455 {
456 	/* draw ops */
457 	unsigned int ngwop = 0;
458 	struct GWOP *curptr;
459 	struct GWOPBLK *blkptr;
460 
461 	/* layers and hypertext */
462 	unsigned plotno = 0;
463 	bool gridline = false;
464 	bool skipplot = false;
465 	bool keysample = false;
466 	bool interactive;
467 	LPWSTR hypertext = NULL;
468 	int hypertype = 0;
469 
470 	/* colors */
471 	bool isColor = true;		/* use colors? */
472 	COLORREF last_color = 0;	/* currently selected color */
473 	double alpha_c = 1.;		/* alpha for transparency */
474 
475 	/* text */
476 	Font * font;
477 
478 	/* lines */
479 	double line_width = lpgw->linewidth;	/* current line width */
480 	double lw_scale = 1.;
481 	LOGPEN cur_penstruct;		/* current pen settings */
482 
483 	/* polylines and polygons */
484 	int polymax = 200;			/* size of ppt */
485 	int polyi = 0;				/* number of points in ppt */
486 	std::vector<PointF> ppt(polymax);	/* storage of polyline/polygon-points */
487 	int last_polyi = 0;			/* number of points in last_poly */
488 	std::vector<PointF> last_poly(0);	/* storage of last filled polygon */
489 	unsigned int lastop = -1;	/* used for plotting last point on a line */
490 
491 	/* filled polygons and boxes */
492 	int last_fillstyle = -1;
493 	COLORREF last_fillcolor = 0;
494 	double last_fill_alpha = 1.;
495 	bool transparent = false;	/* transparent fill? */
496 	Brush * pattern_brush = NULL;
497 	Brush * fill_brush = NULL;
498 	Bitmap * poly_bitmap = NULL;
499 	Graphics * poly_graphics = NULL;
500 	float poly_scale = 2.f;
501 
502 	/* images */
503 	POINT corners[4];			/* image corners */
504 	int color_mode = 0;			/* image color mode */
505 
506 #ifdef EAM_BOXED_TEXT
507 	struct s_boxedtext {
508 		TBOOLEAN boxing;
509 		t_textbox_options option;
510 		POINT margin;
511 		POINT start;
512 		RECT  box;
513 		int   angle;
514 	} boxedtext;
515 #endif
516 
517 	/* point symbols */
518 	enum win_pointtypes last_symbol = W_invalid_pointtype;
519 	CachedBitmap *cb = NULL;
520 	POINT cb_ofs;
521 	bool ps_caching = false;
522 
523 	/* coordinates and lengths */
524 	float xdash, ydash;			/* the transformed coordinates */
525 	int rr, rl, rt, rb;			/* coordinates of drawing area */
526 	int htic, vtic;				/* tic sizes */
527 	float hshift, vshift;			/* correction of text position */
528 
529 	/* indices */
530 	int seq = 0;				/* sequence counter for W_image and W_boxedtext */
531 
532 	if (lpgw->locked) return;
533 
534 	/* clear hypertexts only in display sessions */
535 	interactive = (target == DRAW_SCREEN);
536 	if (interactive)
537 		clear_tooltips(lpgw);
538 
539 	/* Need to scale line widths for raster printers so they are the same
540 	   as on screen */
541 	if (target == DRAW_PRINTER) {
542 		HDC hdc = graphics.GetHDC();
543 		HDC hdc_screen = GetDC(NULL);
544 		lw_scale = (double) GetDeviceCaps(hdc, LOGPIXELSX) /
545 		           (double) GetDeviceCaps(hdc_screen, LOGPIXELSY);
546 		line_width *= lw_scale;
547 		ReleaseDC(NULL, hdc_screen);
548 		graphics.ReleaseHDC(hdc);
549 	}
550 
551 	// only cache point symbols when drawing to a screen
552 	ps_caching = (target == DRAW_SCREEN);
553 
554 	rr = rect->right;
555 	rl = rect->left;
556 	rt = rect->top;
557 	rb = rect->bottom;
558 
559 	if (lpgw->antialiasing) {
560 		graphics.SetSmoothingMode(SmoothingModeAntiAlias8x8);
561 		graphics.SetTextRenderingHint(TextRenderingHintClearTypeGridFit);
562 	}
563 	graphics.SetInterpolationMode(InterpolationModeNearestNeighbor);
564 
565 	/* background fill */
566 	if (isColor)
567 		graphics.Clear(gdiplusCreateColor(lpgw->background, 1.));
568 	else
569 		graphics.Clear(Color(255, 255, 255));
570 
571 	/* Init brush and pens: need to be created after the Graphics object. */
572 	SolidBrush solid_brush(gdiplusCreateColor(lpgw->background, 1.));
573 	SolidBrush solid_fill_brush(gdiplusCreateColor(lpgw->background, 1.));
574 	cur_penstruct = (lpgw->color && isColor) ?  lpgw->colorpen[2] : lpgw->monopen[2];
575 	last_color = cur_penstruct.lopnColor;
576 
577 	Pen pen(Color(0, 0, 0));
578 	Pen solid_pen(Color(0, 0, 0));
579 	pen.SetColor(gdiplusCreateColor(last_color, 1.));
580 	pen.SetLineCap(lpgw->rounded ? LineCapRound : LineCapSquare,
581 					lpgw->rounded ? LineCapRound : LineCapSquare,
582 					DashCapFlat);
583 	pen.SetLineJoin(lpgw->rounded ? LineJoinRound : LineJoinMiter);
584 	solid_pen.SetColor(gdiplusCreateColor(last_color, 1.));
585 	solid_pen.SetLineCap(lpgw->rounded ? LineCapRound : LineCapSquare,
586 					lpgw->rounded ? LineCapRound : LineCapSquare,
587 					DashCapFlat);
588 	solid_pen.SetLineJoin(lpgw->rounded ? LineJoinRound : LineJoinMiter);
589 
590 	htic = (lpgw->org_pointsize * MulDiv(lpgw->htic, rr - rl, lpgw->xmax) + 1);
591 	vtic = (lpgw->org_pointsize * MulDiv(lpgw->vtic, rb - rt, lpgw->ymax) + 1);
592 
593 	lpgw->angle = 0;
594 	lpgw->justify = LEFT;
595 	StringFormat stringFormat(StringFormat::GenericTypographic());
596 	stringFormat.SetAlignment(StringAlignmentNear);
597 	stringFormat.SetLineAlignment(StringAlignmentNear);
598 	INT flags = stringFormat.GetFormatFlags();
599 	flags |= StringFormatFlagsMeasureTrailingSpaces;
600 	stringFormat.SetFormatFlags(flags);
601 	font = SetFont_gdiplus(graphics, rect, lpgw, NULL, 0);
602 
603 	/* calculate text shifting for horizontal text */
604 	hshift = 0.0;
605 	vshift = - lpgw->tmHeight / 2;
606 
607 	/* init layer variables */
608 	lpgw->numplots = 0;
609 	lpgw->hasgrid = FALSE;
610 	for (unsigned i = 0; i < lpgw->maxkeyboxes; i++) {
611 		lpgw->keyboxes[i].left = INT_MAX;
612 		lpgw->keyboxes[i].right = 0;
613 		lpgw->keyboxes[i].bottom = INT_MAX;
614 		lpgw->keyboxes[i].top = 0;
615 	}
616 
617 #ifdef EAM_BOXED_TEXT
618 	boxedtext.boxing = FALSE;
619 #endif
620 
621 	/* do the drawing */
622 	blkptr = lpgw->gwopblk_head;
623 	curptr = NULL;
624 	if (blkptr != NULL) {
625 		if (!blkptr->gwop)
626 			blkptr->gwop = (struct GWOP *) GlobalLock(blkptr->hblk);
627 		if (!blkptr->gwop)
628 			return;
629 		curptr = (struct GWOP *)blkptr->gwop;
630 	}
631 	if (curptr == NULL)
632 		return;
633 
634 	while (ngwop < lpgw->nGWOP) {
635 		// transform the coordinates
636 		if (lpgw->oversample) {
637 			xdash = float(curptr->x) * (rr - rl - 1) / float(lpgw->xmax) + rl;
638 			ydash = float(rb) - float(curptr->y) * (rb - rt - 1) / float(lpgw->ymax) + rt - 1;
639 		} else {
640 			xdash = MulDiv(curptr->x, rr - rl - 1, lpgw->xmax) + rl;
641 			ydash = rb - MulDiv(curptr->y, rb - rt - 1, lpgw->ymax) + rt - 1;
642 		}
643 
644 		/* finish last filled polygon */
645 		if ((!last_poly.empty()) &&
646 			(((lastop == W_filled_polygon_draw) && (curptr->op != W_fillstyle)) ||
647 			 ((curptr->op == W_fillstyle) && (curptr->x != unsigned(last_fillstyle))))) {
648 			if (poly_graphics == NULL) {
649 				// changing smoothing mode is necessary in case of new/unknown code paths
650 				SmoothingMode mode = graphics.GetSmoothingMode();
651 				if (lpgw->antialiasing && !lpgw->polyaa)
652 					graphics.SetSmoothingMode(SmoothingModeNone);
653 				graphics.FillPolygon(fill_brush, last_poly.data(), last_polyi);
654 				graphics.SetSmoothingMode(mode);
655 			} else {
656 				poly_graphics->FillPolygon(fill_brush, last_poly.data(), last_polyi);
657 			}
658 			last_polyi = 0;
659 			last_poly.clear();
660 		}
661 
662 		/* handle layer commands first */
663 		if (curptr->op == W_layer) {
664 			t_termlayer layer = (t_termlayer) curptr->x;
665 			switch (layer) {
666 				case TERM_LAYER_BEFORE_PLOT:
667 					plotno++;
668 					lpgw->numplots = plotno;
669 					if (plotno >= lpgw->maxhideplots) {
670 						unsigned int idx;
671 						lpgw->maxhideplots += 10;
672 						lpgw->hideplot = (BOOL *) realloc(lpgw->hideplot, lpgw->maxhideplots * sizeof(BOOL));
673 						for (idx = plotno; idx < lpgw->maxhideplots; idx++)
674 							lpgw->hideplot[idx] = FALSE;
675 					}
676 					if (plotno <= lpgw->maxhideplots)
677 						skipplot = (lpgw->hideplot[plotno - 1] > 0);
678 					break;
679 				case TERM_LAYER_AFTER_PLOT:
680 					skipplot = false;
681 					break;
682 #if 0
683 				case TERM_LAYER_BACKTEXT:
684 				case TERM_LAYER_FRONTTEXT
685 				case TERM_LAYER_END_TEXT:
686 					break;
687 #endif
688 				case TERM_LAYER_BEGIN_GRID:
689 					gridline = true;
690 					lpgw->hasgrid = TRUE;
691 					break;
692 				case TERM_LAYER_END_GRID:
693 					gridline = false;
694 					break;
695 				case TERM_LAYER_BEGIN_KEYSAMPLE:
696 					keysample = true;
697 					break;
698 				case TERM_LAYER_END_KEYSAMPLE:
699 					/* grey out keysample if graph is hidden */
700 					if ((plotno <= lpgw->maxhideplots) && lpgw->hideplot[plotno - 1]) {
701 						ARGB argb = Color::MakeARGB(128, 192, 192, 192);
702 						Color transparentgrey(argb);
703 						SolidBrush greybrush(transparentgrey);
704 						LPRECT bb = lpgw->keyboxes + plotno - 1;
705 						graphics.FillRectangle(&greybrush, INT(bb->left), INT(bb->bottom),
706 						                       bb->right - bb->left, bb->top - bb->bottom);
707 					}
708 					keysample = false;
709 					break;
710 				case TERM_LAYER_RESET:
711 				case TERM_LAYER_RESET_PLOTNO:
712 					plotno = 0;
713 					break;
714 				case TERM_LAYER_BEGIN_PM3D_MAP:
715 				case TERM_LAYER_BEGIN_PM3D_FLUSH:
716 					// Antialiasing of pm3d polygons is obtained by drawing to a
717 					// bitmap four times as large and copying it back with interpolation
718 					if (lpgw->antialiasing && lpgw->polyaa) {
719 						poly_bitmap = new Bitmap(poly_scale * (rr - rl), poly_scale * (rb - rt), &graphics);
720 						poly_graphics = Graphics::FromImage(poly_bitmap);
721 						poly_graphics->SetSmoothingMode(SmoothingModeNone);
722 						Matrix transform(poly_scale, 0.0f, 0.0f, poly_scale, 0.0f, 0.0f);
723 						poly_graphics->SetTransform(&transform);
724 					}
725 					break;
726 				case TERM_LAYER_END_PM3D_MAP:
727 				case TERM_LAYER_END_PM3D_FLUSH:
728 					if (poly_graphics != NULL) {
729 						delete poly_graphics;
730 						poly_graphics = NULL;
731 						graphics.SetInterpolationMode(InterpolationModeHighQualityBilinear);
732 						graphics.SetPixelOffsetMode(PixelOffsetModeHighQuality);
733 						graphics.DrawImage(poly_bitmap, 0, 0, rr - rl, rb - rt);
734 						graphics.SetInterpolationMode(InterpolationModeNearestNeighbor);
735 						graphics.SetPixelOffsetMode(PixelOffsetModeNone);
736 						delete poly_bitmap;
737 					}
738 					break;
739 				case TERM_LAYER_BEGIN_IMAGE:
740 				case TERM_LAYER_BEGIN_COLORBOX:
741 					// Turn of antialiasing for failsafe/pixel images and color boxes
742 					// to avoid seams.
743 					if (lpgw->antialiasing)
744 						graphics.SetSmoothingMode(SmoothingModeNone);
745 					break;
746 				case TERM_LAYER_END_IMAGE:
747 				case TERM_LAYER_END_COLORBOX:
748 					if (lpgw->antialiasing)
749 						graphics.SetSmoothingMode(SmoothingModeAntiAlias8x8);
750 					break;
751 				default:
752 					break;
753 			};
754 		}
755 
756 		/* Hide this layer? Do not skip commands which could affect key samples: */
757 		if (!(skipplot || (gridline && lpgw->hidegrid)) ||
758 			keysample || (curptr->op == W_line_type) || (curptr->op == W_setcolor)
759 			          || (curptr->op == W_pointsize) || (curptr->op == W_line_width)
760 			          || (curptr->op == W_dash_type)) {
761 
762 		/* special case hypertexts */
763 		if ((hypertext != NULL) && (hypertype == TERM_HYPERTEXT_TOOLTIP)) {
764 			/* point symbols */
765 			if ((curptr->op >= W_dot) && (curptr->op <= W_dot + WIN_POINT_TYPES)) {
766 				RECT rect;
767 				rect.left = xdash - htic - 0.5;
768 				rect.right = xdash + htic + 0.5;
769 				rect.top = ydash - vtic - 0.5;
770 				rect.bottom = ydash + vtic + 0.5;
771 				add_tooltip(lpgw, &rect, hypertext);
772 				hypertext = NULL;
773 			}
774 		}
775 
776 		switch (curptr->op) {
777 		case 0:	/* have run past last in this block */
778 			break;
779 
780 		case W_layer: /* already handled above */
781 			break;
782 
783 		case W_polyline: {
784 			POINTL * poly = (POINTL *) LocalLock(curptr->htext);
785 			polyi = curptr->x;
786 			PointF * points = new PointF[polyi];
787 			for (int i = 0; i < polyi; i++) {
788 				// transform the coordinates
789 				if (lpgw->oversample) {
790 					points[i].X = float(poly[i].x) * (rr - rl - 1) / float(lpgw->xmax) + rl;
791 					points[i].Y = float(rb) - float(poly[i].y) * (rb - rt - 1) / float(lpgw->ymax) + rt - 1;
792 				} else {
793 					points[i].X = MulDiv(poly[i].x, rr - rl - 1, lpgw->xmax) + rl;
794 					points[i].Y = rb - MulDiv(poly[i].y, rb - rt - 1, lpgw->ymax) + rt - 1;
795 				}
796 			}
797 			LocalUnlock(poly);
798 			if (poly_graphics == NULL)
799 				gdiplusPolyline(graphics, pen, points, polyi);
800 			else
801 				gdiplusPolyline(*poly_graphics, pen, points, polyi);
802 			if (keysample) {
803 				draw_update_keybox(lpgw, plotno, points[0].X, points[0].Y);
804 				draw_update_keybox(lpgw, plotno, points[polyi - 1].X, points[polyi - 1].Y);
805 			}
806 			delete [] points;
807 			break;
808 		}
809 
810 		case W_line_type: {
811 			int cur_pen = (int)curptr->x % WGNUMPENS;
812 
813 			/* create new pens */
814 			if (cur_pen > LT_NODRAW) {
815 				cur_pen += 2;
816 				cur_penstruct.lopnWidth =
817 					(lpgw->color && isColor) ? lpgw->colorpen[cur_pen].lopnWidth : lpgw->monopen[cur_pen].lopnWidth;
818 				cur_penstruct.lopnColor = lpgw->colorpen[cur_pen].lopnColor;
819 				if (!lpgw->color || !isColor) {
820 					COLORREF color = cur_penstruct.lopnColor;
821 					unsigned luma = luma_from_color(GetRValue(color), GetGValue(color), GetBValue(color));
822 					cur_penstruct.lopnColor = RGB(luma, luma, luma);
823 				}
824 				cur_penstruct.lopnStyle =
825 					lpgw->dashed ? lpgw->monopen[cur_pen].lopnStyle : lpgw->colorpen[cur_pen].lopnStyle;
826 			} else if (cur_pen == LT_NODRAW) {
827 				cur_pen = WGNUMPENS;
828 				cur_penstruct.lopnStyle = PS_NULL;
829 				cur_penstruct.lopnColor = 0;
830 				cur_penstruct.lopnWidth.x = 1;
831 			} else { /* <= LT_BACKGROUND */
832 				cur_pen = WGNUMPENS;
833 				cur_penstruct.lopnStyle = PS_SOLID;
834 				cur_penstruct.lopnColor = lpgw->background;
835 				cur_penstruct.lopnWidth.x = 1;
836 			}
837 			cur_penstruct.lopnWidth.x *= line_width;
838 
839 			Color color = gdiplusCreateColor(cur_penstruct.lopnColor, 1.);
840 			solid_brush.SetColor(color);
841 
842 			solid_pen.SetColor(color);
843 			solid_pen.SetWidth(cur_penstruct.lopnWidth.x);
844 
845 			pen.SetColor(color);
846 			pen.SetWidth(cur_penstruct.lopnWidth.x);
847 			if (cur_penstruct.lopnStyle <= PS_DASHDOTDOT)
848 				// cast is safe since GDI and GDI+ use the same numbers
849 				gdiplusSetDashStyle(&pen, static_cast<DashStyle>(cur_penstruct.lopnStyle));
850 			else
851 				pen.SetDashStyle(DashStyleSolid);
852 
853 			/* remember this color */
854 			last_color = cur_penstruct.lopnColor;
855 			alpha_c = 1.;
856 			break;
857 		}
858 
859 		case W_dash_type: {
860 			int dt = static_cast<int>(curptr->x);
861 
862 			if (dt >= 0) {
863 				dt %= WGNUMPENS;
864 				dt += 2;
865 				cur_penstruct.lopnStyle = lpgw->monopen[dt].lopnStyle;
866 				gdiplusSetDashStyle(&pen, static_cast<DashStyle>(cur_penstruct.lopnStyle));
867 			} else if (dt == DASHTYPE_SOLID) {
868 				cur_penstruct.lopnStyle = PS_SOLID;
869 				gdiplusSetDashStyle(&pen, static_cast<DashStyle>(cur_penstruct.lopnStyle));
870 			} else if (dt == DASHTYPE_AXIS) {
871 				dt = 1;
872 				cur_penstruct.lopnStyle =
873 					lpgw->dashed ? lpgw->monopen[dt].lopnStyle : lpgw->colorpen[dt].lopnStyle;
874 				gdiplusSetDashStyle(&pen, static_cast<DashStyle>(cur_penstruct.lopnStyle));
875 			} else if (dt == DASHTYPE_CUSTOM) {
876 				t_dashtype * dash = static_cast<t_dashtype *>(LocalLock(curptr->htext));
877 				INT count = 0;
878 				while ((dash->pattern[count] != 0.) && (count < DASHPATTERN_LENGTH)) count++;
879 				pen.SetDashPattern(dash->pattern, count);
880 				LocalUnlock(curptr->htext);
881 			}
882 			break;
883 		}
884 
885 		case W_text_encoding:
886 			lpgw->encoding = (set_encoding_id) curptr->x;
887 			break;
888 
889 		case W_put_text: {
890 			char * str;
891 			str = (char *) LocalLock(curptr->htext);
892 			if (str) {
893 				LPWSTR textw = UnicodeText(str, lpgw->encoding);
894 				if (textw) {
895 					if (lpgw->angle == 0) {
896 						PointF pointF(xdash + hshift, ydash + vshift);
897 						graphics.DrawString(textw, -1, font, pointF, &stringFormat, &solid_brush);
898 					} else {
899 						/* shift rotated text correctly */
900 						graphics.TranslateTransform(xdash + hshift, ydash + vshift);
901 						graphics.RotateTransform(-lpgw->angle);
902 						graphics.DrawString(textw, -1, font, PointF(0,0), &stringFormat, &solid_brush);
903 						graphics.ResetTransform();
904 					}
905 					RectF size;
906 					int dxl, dxr;
907 #ifndef EAM_BOXED_TEXT
908 					if (keysample) {
909 #else
910 					if (keysample || boxedtext.boxing) {
911 #endif
912 						graphics.MeasureString(textw, -1, font, PointF(0,0), &stringFormat, &size);
913 						if (lpgw->justify == LEFT) {
914 							dxl = 0;
915 							dxr = size.Width + 0.5;
916 						} else if (lpgw->justify == CENTRE) {
917 							dxl = dxr = size.Width / 2;
918 						} else {
919 							dxl = size.Width + 0.5;
920 							dxr = 0;
921 						}
922 					}
923 					if (keysample) {
924 						draw_update_keybox(lpgw, plotno, xdash - dxl, ydash - size.Height / 2);
925 						draw_update_keybox(lpgw, plotno, xdash + dxr, ydash + size.Height / 2);
926 					}
927 #ifdef EAM_BOXED_TEXT
928 					if (boxedtext.boxing) {
929 						if (boxedtext.box.left > (xdash - boxedtext.start.x - dxl))
930 							boxedtext.box.left = xdash - boxedtext.start.x - dxl;
931 						if (boxedtext.box.right < (xdash - boxedtext.start.x + dxr))
932 							boxedtext.box.right = xdash - boxedtext.start.x + dxr;
933 						if (boxedtext.box.top > (ydash - boxedtext.start.y - size.Height / 2))
934 							boxedtext.box.top = ydash - boxedtext.start.y - size.Height / 2;
935 						if (boxedtext.box.bottom < (ydash - boxedtext.start.y + size.Height / 2))
936 							boxedtext.box.bottom = ydash - boxedtext.start.y + size.Height / 2;
937 						/* We have to remember the text angle as well. */
938 						boxedtext.angle = lpgw->angle;
939 					}
940 #endif
941 					free(textw);
942 				}
943 			}
944 			LocalUnlock(curptr->htext);
945 			break;
946 		}
947 
948 		case W_enhanced_text: {
949 			char * str = (char *) LocalLock(curptr->htext);
950 			if (str) {
951 				RECT extend;
952 				draw_enhanced_init(lpgw, graphics, solid_brush, rect);
953 				draw_enhanced_text(lpgw, rect, xdash, ydash, str);
954 				draw_get_enhanced_text_extend(&extend);
955 
956 				if (keysample) {
957 					draw_update_keybox(lpgw, plotno, xdash - extend.left, ydash - extend.top);
958 					draw_update_keybox(lpgw, plotno, xdash + extend.right, ydash + extend.bottom);
959 				}
960 #ifdef EAM_BOXED_TEXT
961 				if (boxedtext.boxing) {
962 					if (boxedtext.box.left > (boxedtext.start.x - xdash - extend.left))
963 						boxedtext.box.left = boxedtext.start.x - xdash - extend.left;
964 					if (boxedtext.box.right < (boxedtext.start.x - xdash + extend.right))
965 						boxedtext.box.right = boxedtext.start.x - xdash + extend.right;
966 					if (boxedtext.box.top > (boxedtext.start.y - ydash - extend.top))
967 						boxedtext.box.top = boxedtext.start.y - ydash - extend.top;
968 					if (boxedtext.box.bottom < (boxedtext.start.y - ydash + extend.bottom))
969 						boxedtext.box.bottom = boxedtext.start.y - ydash + extend.bottom;
970 					/* We have to store the text angle as well. */
971 					boxedtext.angle = lpgw->angle;
972 				}
973 #endif
974 			}
975 			LocalUnlock(curptr->htext);
976 			break;
977 		}
978 
979 		case W_hypertext:
980 			if (interactive) {
981 				/* Make a copy for future reference */
982 				char * str = (char *) LocalLock(curptr->htext);
983 				free(hypertext);
984 				hypertext = UnicodeText(str, lpgw->encoding);
985 				hypertype = curptr->x;
986 				LocalUnlock(curptr->htext);
987 			}
988 			break;
989 
990 #ifdef EAM_BOXED_TEXT
991 		case W_boxedtext:
992 			if (seq == 0) {
993 				boxedtext.option = (t_textbox_options) curptr->x;
994 				seq++;
995 				break;
996 			}
997 			seq = 0;
998 			switch (boxedtext.option) {
999 			case TEXTBOX_INIT:
1000 				/* initialise bounding box */
1001 				boxedtext.box.left   = boxedtext.box.right = 0;
1002 				boxedtext.box.bottom = boxedtext.box.top   = 0;
1003 				boxedtext.start.x = xdash;
1004 				boxedtext.start.y = ydash;
1005 				/* Note: initialising the text angle here would be best IMHO,
1006 				   but current core code does not set this until the actual
1007 				   print-out is done. */
1008 				boxedtext.angle = lpgw->angle;
1009 				boxedtext.boxing = TRUE;
1010 				break;
1011 			case TEXTBOX_OUTLINE:
1012 			case TEXTBOX_BACKGROUNDFILL: {
1013 				/* draw rectangle */
1014 				int dx = boxedtext.margin.x;
1015 				int dy = boxedtext.margin.y;
1016 				if ((boxedtext.angle % 90) == 0) {
1017 					Rect rect;
1018 
1019 					switch (boxedtext.angle) {
1020 					case 0:
1021 						rect.X      = + boxedtext.box.left;
1022 						rect.Y      = + boxedtext.box.top;
1023 						rect.Width  = boxedtext.box.right - boxedtext.box.left;
1024 						rect.Height = boxedtext.box.bottom - boxedtext.box.top;
1025 						rect.Inflate(dx, dy);
1026 						break;
1027 					case 90:
1028 						rect.X      = + boxedtext.box.top;
1029 						rect.Y      = - boxedtext.box.right;
1030 						rect.Height = boxedtext.box.right - boxedtext.box.left;
1031 						rect.Width  = boxedtext.box.bottom - boxedtext.box.top;
1032 						rect.Inflate(dy, dx);
1033 						break;
1034 					case 180:
1035 						rect.X      = - boxedtext.box.right;
1036 						rect.Y      = - boxedtext.box.bottom;
1037 						rect.Width  = boxedtext.box.right - boxedtext.box.left;
1038 						rect.Height = boxedtext.box.bottom - boxedtext.box.top;
1039 						rect.Inflate(dx, dy);
1040 						break;
1041 					case 270:
1042 						rect.X      = - boxedtext.box.bottom;
1043 						rect.Y      = + boxedtext.box.left;
1044 						rect.Height = boxedtext.box.right - boxedtext.box.left;
1045 						rect.Width  = boxedtext.box.bottom - boxedtext.box.top;
1046 						rect.Inflate(dy, dx);
1047 						break;
1048 					}
1049 					rect.Offset(boxedtext.start.x, boxedtext.start.y);
1050 					if (boxedtext.option == TEXTBOX_OUTLINE) {
1051 						graphics.DrawRectangle(&pen, rect);
1052 					} else {
1053 						/* Fill bounding box with current color. */
1054 						graphics.FillRectangle(&solid_brush, rect);
1055 					}
1056 				} else {
1057 					float theta = boxedtext.angle * M_PI / 180.;
1058 					float sin_theta = sin(theta);
1059 					float cos_theta = cos(theta);
1060 					PointF rect[5];
1061 
1062 					rect[0].X =  (boxedtext.box.left   - dx) * cos_theta +
1063 								 (boxedtext.box.top    - dy) * sin_theta;
1064 					rect[0].Y = -(boxedtext.box.left   - dx) * sin_theta +
1065 								 (boxedtext.box.top    - dy) * cos_theta;
1066 					rect[1].X =  (boxedtext.box.left   - dx) * cos_theta +
1067 								 (boxedtext.box.bottom + dy) * sin_theta;
1068 					rect[1].Y = -(boxedtext.box.left   - dx) * sin_theta +
1069 								 (boxedtext.box.bottom + dy) * cos_theta;
1070 					rect[2].X =  (boxedtext.box.right  + dx) * cos_theta +
1071 								 (boxedtext.box.bottom + dy) * sin_theta;
1072 					rect[2].Y = -(boxedtext.box.right  + dx) * sin_theta +
1073 								 (boxedtext.box.bottom + dy) * cos_theta;
1074 					rect[3].X =  (boxedtext.box.right  + dx) * cos_theta +
1075 								 (boxedtext.box.top    - dy) * sin_theta;
1076 					rect[3].Y = -(boxedtext.box.right  + dx) * sin_theta +
1077 								 (boxedtext.box.top    - dy) * cos_theta;
1078 					for (int i = 0; i < 4; i++) {
1079 						rect[i].X += boxedtext.start.x;
1080 						rect[i].Y += boxedtext.start.y;
1081 					}
1082 					if (boxedtext.option == TEXTBOX_OUTLINE) {
1083 						rect[4].X = rect[0].X;
1084 						rect[4].Y = rect[0].Y;
1085 						gdiplusPolyline(graphics, pen, rect, 5);
1086 					} else {
1087 						graphics.FillPolygon(fill_brush, rect, 4);
1088 					}
1089 				}
1090 				boxedtext.boxing = FALSE;
1091 				break;
1092 			}
1093 			case TEXTBOX_MARGINS:
1094 				/* Adjust size of whitespace around text: default is 1/2 char height + 2 char widths. */
1095 				boxedtext.margin.x = MulDiv(curptr->x * lpgw->hchar, rr - rl, 1000 * lpgw->xmax);
1096 				boxedtext.margin.y = MulDiv(curptr->y * lpgw->hchar, rr - rl, 1000 * lpgw->xmax);
1097 				break;
1098 			default:
1099 				break;
1100 			}
1101 			break;
1102 #endif
1103 
1104 		case W_fillstyle: {
1105 			/* HBB 20010916: new entry, needed to squeeze the many
1106 			 * parameters of a filled box call through the bottleneck
1107 			 * of the fixed number of parameters in GraphOp() and
1108 			 * struct GWOP, respectively. */
1109 			/* FIXME: resetting polyi here should not be necessary */
1110 			polyi = 0; /* start new sequence */
1111 			int fillstyle = curptr->x;
1112 
1113 			/* Eliminate duplicate fillstyle requests. */
1114 			if ((fillstyle == last_fillstyle) &&
1115 				(last_color == last_fillcolor) &&
1116 				(last_fill_alpha == alpha_c))
1117 				break;
1118 
1119 			transparent = false;
1120 			switch (fillstyle & 0x0f) {
1121 				case FS_TRANSPARENT_SOLID: {
1122 					double alpha = (fillstyle >> 4) / 100.;
1123 					solid_fill_brush.SetColor(gdiplusCreateColor(last_color, alpha));
1124 					fill_brush = &solid_fill_brush;
1125 					break;
1126 				}
1127 				case FS_SOLID: {
1128 					if (alpha_c < 1.) {
1129 						solid_fill_brush.SetColor(gdiplusCreateColor(last_color, alpha_c));
1130 					} else if ((int)(fillstyle >> 4) == 100) {
1131 						/* special case this common choice */
1132 						solid_fill_brush.SetColor(gdiplusCreateColor(last_color, 1.));
1133 					} else {
1134 						double density = MINMAX(0, (int)(fillstyle >> 4), 100) * 0.01;
1135 						COLORREF color =
1136 							RGB(255 - density * (255 - GetRValue(last_color)),
1137 								255 - density * (255 - GetGValue(last_color)),
1138 								255 - density * (255 - GetBValue(last_color)));
1139 						solid_fill_brush.SetColor(gdiplusCreateColor(color, 1.));
1140 					}
1141 					fill_brush = &solid_fill_brush;
1142 					break;
1143 				}
1144 				case FS_TRANSPARENT_PATTERN:
1145 					transparent = true;
1146 					/* intentionally fall through */
1147 				case FS_PATTERN: {
1148 					/* style == 2 --> use fill pattern according to
1149 							 * fillpattern. Pattern number is enumerated */
1150 					int pattern = GPMAX(fillstyle >> 4, 0) % pattern_num;
1151 					if (pattern_brush)
1152 						delete pattern_brush;
1153 					pattern_brush = gdiplusPatternBrush(pattern,
1154 									last_color, 1., lpgw->background, transparent);
1155 					fill_brush = pattern_brush;
1156 					break;
1157 				}
1158 				case FS_EMPTY:
1159 					/* FIXME: Instead of filling with background color, we should not fill at all in this case! */
1160 					/* fill with background color */
1161 					solid_fill_brush.SetColor(gdiplusCreateColor(lpgw->background, 1.));
1162 					fill_brush = &solid_fill_brush;
1163 					break;
1164 				case FS_DEFAULT:
1165 				default:
1166 					/* Leave the current brush and color in place */
1167 					solid_fill_brush.SetColor(gdiplusCreateColor(last_color, 1.));
1168 					fill_brush = &solid_fill_brush;
1169 					break;
1170 			}
1171 			last_fillstyle = fillstyle;
1172 			last_fillcolor = last_color;
1173 			last_fill_alpha = alpha_c;
1174 			break;
1175 		}
1176 
1177 		case W_move:
1178 			// This is used for filled boxes only.
1179 			ppt[0].X = xdash;
1180 			ppt[0].Y = ydash;
1181 			break;
1182 
1183 		case W_boxfill: {
1184 			/* NOTE: the x and y passed with this call are the coordinates of the
1185 			 * lower right corner of the box. The upper left corner was stored into
1186 			 * ppt[0] by a preceding W_move, and the style was set
1187 			 * by a W_fillstyle call. */
1188 			// snap to pixel grid
1189 			Point p1(xdash + 0.5, ydash + 0.5);
1190 			Point p2(ppt[0].X + 0.5, ppt[0].Y + 0.5);
1191 			Point p;
1192 			int height, width;
1193 
1194 			p.X = GPMIN(p1.X, p2.X);
1195 			p.Y = GPMIN(p1.Y, p2.Y);
1196 			width = abs(p2.X - p1.X);
1197 			height = abs(p1.Y - p2.Y);
1198 
1199 			SmoothingMode mode = graphics.GetSmoothingMode();
1200 			graphics.SetSmoothingMode(SmoothingModeNone);
1201 			graphics.FillRectangle(fill_brush, p.X, p.Y, width, height);
1202 			graphics.SetSmoothingMode(mode);
1203 			if (keysample)
1204 				draw_update_keybox(lpgw, plotno, xdash + 1, ydash);
1205 			polyi = 0;
1206 			break;
1207 		}
1208 
1209 		case W_text_angle:
1210 			if (lpgw->angle != (int)curptr->x) {
1211 				lpgw->angle = (int)curptr->x;
1212 				/* recalculate shifting of rotated text */
1213 				hshift = - sin(M_PI / 180. * lpgw->angle) * lpgw->tmHeight / 2.;
1214 				vshift = - cos(M_PI / 180. * lpgw->angle) * lpgw->tmHeight / 2.;
1215 				if (lpgw->antialiasing) {
1216 					// Cleartype is only applied to non-rotated text
1217 					if ((lpgw->angle % 180) != 0)
1218 						graphics.SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);
1219 					else
1220 						graphics.SetTextRenderingHint(TextRenderingHintClearTypeGridFit);
1221 				}
1222 			}
1223 			break;
1224 
1225 		case W_justify:
1226 			switch (curptr->x) {
1227 				case LEFT:
1228 					stringFormat.SetAlignment(StringAlignmentNear);
1229 					break;
1230 				case RIGHT:
1231 					stringFormat.SetAlignment(StringAlignmentFar);
1232 					break;
1233 				case CENTRE:
1234 					stringFormat.SetAlignment(StringAlignmentCenter);
1235 					break;
1236 			}
1237 			lpgw->justify = curptr->x;
1238 			break;
1239 
1240 		case W_font: {
1241 			int size = curptr->x;
1242 			char * fontname = (char *) LocalLock(curptr->htext);
1243 			delete font;
1244 #ifdef UNICODE
1245 			/* FIXME: Maybe this should be in win.trm instead. */
1246 			LPWSTR wfontname = UnicodeText(fontname, lpgw->encoding);
1247 			font = SetFont_gdiplus(graphics, rect, lpgw, wfontname, size);
1248 			free(wfontname);
1249 #else
1250 			font = SetFont_gdiplus(graphics, rect, lpgw, fontname, size);
1251 #endif
1252 			LocalUnlock(curptr->htext);
1253 			/* recalculate shifting of rotated text */
1254 			hshift = - sin(M_PI / 180. * lpgw->angle) * lpgw->tmHeight / 2.;
1255 			vshift = - cos(M_PI / 180. * lpgw->angle) * lpgw->tmHeight / 2.;
1256 			break;
1257 		}
1258 
1259 		case W_pointsize:
1260 			if (curptr->x > 0) {
1261 				double pointsize = curptr->x / 100.0;
1262 				htic = MulDiv(pointsize * lpgw->pointscale * lpgw->htic, rr - rl, lpgw->xmax) + 1;
1263 				vtic = MulDiv(pointsize * lpgw->pointscale * lpgw->vtic, rb - rt, lpgw->ymax) + 1;
1264 			} else {
1265 				htic = vtic = 0;
1266 			}
1267 			/* invalidate point symbol cache */
1268 			last_symbol = W_invalid_pointtype;
1269 			break;
1270 
1271 		case W_line_width:
1272 			/* HBB 20000813: this may look strange, but it ensures
1273 			 * that linewidth is exactly 1 iff it's in default
1274 			 * state */
1275 			line_width = curptr->x == 100 ? 1 : (curptr->x / 100.0);
1276 			line_width *= lpgw->linewidth * lw_scale;
1277 			if (poly_graphics != NULL)
1278 				line_width *= poly_scale;
1279 			solid_pen.SetWidth(line_width);
1280 			pen.SetWidth(line_width);
1281 			/* invalidate point symbol cache */
1282 			last_symbol = W_invalid_pointtype;
1283 			break;
1284 
1285 		case W_setcolor: {
1286 			COLORREF color;
1287 
1288 			/* distinguish gray values and RGB colors */
1289 			if (curptr->htext != NULL) {	/* TC_LT */
1290 				int pen = (int)curptr->x % WGNUMPENS;
1291 				color = (pen <= LT_NODRAW) ? lpgw->background : lpgw->colorpen[pen + 2].lopnColor;
1292 				if (!lpgw->color || !isColor) {
1293 					unsigned luma = luma_from_color(GetRValue(color), GetGValue(color), GetBValue(color));
1294 					color = RGB(luma, luma, luma);
1295 				}
1296 				alpha_c = 1.;
1297 			} else {					/* TC_RGB */
1298 				rgb255_color rgb255;
1299 				rgb255.r = (curptr->y & 0xff);
1300 				rgb255.g = (curptr->x >> 8);
1301 				rgb255.b = (curptr->x & 0xff);
1302 				alpha_c = 1. - ((curptr->y >> 8) & 0xff) / 255.;
1303 
1304 				if (lpgw->color || ((rgb255.r == rgb255.g) && (rgb255.r == rgb255.b))) {
1305 					/* Use colors or this is already gray scale */
1306 					color = RGB(rgb255.r, rgb255.g, rgb255.b);
1307 				} else {
1308 					/* convert to gray */
1309 					unsigned luma = luma_from_color(rgb255.r, rgb255.g, rgb255.b);
1310 					color = RGB(luma, luma, luma);
1311 				}
1312 			}
1313 
1314 			/* update brushes and pens */
1315 			Color pcolor = gdiplusCreateColor(color, alpha_c);
1316 			solid_brush.SetColor(pcolor);
1317 			pen.SetColor(pcolor);
1318 			solid_pen.SetColor(pcolor);
1319 
1320 			/* invalidate point symbol cache */
1321 			if (last_color != color)
1322 				last_symbol = W_invalid_pointtype;
1323 
1324 			/* remember this color */
1325 			cur_penstruct.lopnColor = color;
1326 			last_color = color;
1327 			break;
1328 		}
1329 
1330 		case W_filled_polygon_pt: {
1331 			/* a point of the polygon is coming */
1332 			if (polyi >= polymax) {
1333 				polymax += 200;
1334 				ppt.reserve(polymax + 1);
1335 			}
1336 			ppt[polyi].X = xdash;
1337 			ppt[polyi].Y = ydash;
1338 			polyi++;
1339 			break;
1340 		}
1341 
1342 		case W_filled_polygon_draw: {
1343 			bool found = false;
1344 			int i, k;
1345 			//bool same_rot = true;
1346 
1347 			// Test if successive polygons share a common edge:
1348 			if ((!last_poly.empty()) && (polyi > 2)) {
1349 				// Check for a common edge with previous filled polygon.
1350 				for (i = 0; (i < polyi) && !found; i++) {
1351 					for (k = 0; (k < last_polyi) && !found; k++) {
1352 						if ((ppt[i].X == last_poly[k].X) && (ppt[i].Y == last_poly[k].Y)) {
1353 							if ((ppt[(i + 1) % polyi].X == last_poly[(k + 1) % last_polyi].X) &&
1354 							    (ppt[(i + 1) % polyi].Y == last_poly[(k + 1) % last_polyi].Y)) {
1355 								//found = true;
1356 								//same_rot = true;
1357 							}
1358 							// This is the dominant case for filling between curves,
1359 							// see fillbetween.dem and polar.dem.
1360 							if ((ppt[(i + 1) % polyi].X == last_poly[(k + last_polyi - 1) % last_polyi].X) &&
1361 							    (ppt[(i + 1) % polyi].Y == last_poly[(k + last_polyi - 1) % last_polyi].Y)) {
1362 								found = true;
1363 								//same_rot = false;
1364 							}
1365 						}
1366 					}
1367 				}
1368 			}
1369 
1370 			if (found) { // merge polygons
1371 				// rewind
1372 				i--; k--;
1373 
1374 				int extra = polyi - 2;
1375 				// extend buffer to make room for extra points
1376 				last_poly.reserve(last_polyi + extra + 1);
1377 				/* TODO: should use memmove instead */
1378 				for (int n = last_polyi - 1; n >= k; n--) {
1379 					last_poly[n + extra].X = last_poly[n].X;
1380 					last_poly[n + extra].Y = last_poly[n].Y;
1381 				}
1382 				// copy new points
1383 				for (int n = 0; n < extra; n++) {
1384 					last_poly[k + n].X = ppt[(i + 2 + n) % polyi].X;
1385 					last_poly[k + n].Y = ppt[(i + 2 + n) % polyi].Y;
1386 				}
1387 				last_polyi += extra;
1388 			} else {
1389 				if (!last_poly.empty()) {
1390 					if (poly_graphics == NULL) {
1391 						// changing smoothing mode is still necessary in case of new/unknown code paths
1392 						SmoothingMode mode = graphics.GetSmoothingMode();
1393 						if (lpgw->antialiasing && !lpgw->polyaa)
1394 							graphics.SetSmoothingMode(SmoothingModeNone);
1395 						graphics.FillPolygon(fill_brush, last_poly.data(), last_polyi);
1396 						graphics.SetSmoothingMode(mode);
1397 					} else {
1398 						poly_graphics->FillPolygon(fill_brush, last_poly.data(), last_polyi);
1399 					}
1400 				}
1401 				// save the current polygon
1402 				last_poly = ppt;
1403 				last_polyi = polyi;
1404 			}
1405 
1406 			polyi = 0;
1407 			break;
1408 		}
1409 
1410 		case W_image:	{
1411 			/* Due to the structure of gwop 6 entries are needed in total. */
1412 			if (seq == 0) {
1413 				/* First OP contains only the color mode */
1414 				color_mode = curptr->x;
1415 			} else if (seq < 5) {
1416 				/* Next four OPs contain the `corner` array */
1417 				corners[seq - 1].x = xdash + 0.5;
1418 				corners[seq - 1].y = ydash + 0.5;
1419 			} else {
1420 				/* The last OP contains the image and it's size */
1421 				char * image = (char *) LocalLock(curptr->htext);
1422 				unsigned int width = curptr->x;
1423 				unsigned int height = curptr->y;
1424 				if (image) {
1425 					Bitmap * bitmap;
1426 
1427 					graphics.SetPixelOffsetMode(PixelOffsetModeHighQuality);
1428 
1429 					/* create clip region */
1430 					Rect clipRect(
1431 						(INT) GPMIN(corners[2].x, corners[3].x), (INT) GPMIN(corners[2].y, corners[3].y),
1432 						abs(corners[2].x - corners[3].x), abs(corners[2].y - corners[3].y));
1433 					graphics.SetClip(clipRect);
1434 
1435 					if (color_mode != IC_RGBA) {
1436 						int pad_bytes = (4 - (3 * width) % 4) % 4; /* scan lines start on ULONG boundaries */
1437 						int stride = width * 3 + pad_bytes;
1438 						bitmap = new Bitmap(width, height, stride, PixelFormat24bppRGB, (BYTE *) image);
1439 					} else {
1440 						int stride = width * 4;
1441 						bitmap = new Bitmap(width, height, stride, PixelFormat32bppARGB, (BYTE *) image);
1442 					}
1443 
1444 					if (bitmap) {
1445 						/* image is upside-down */
1446 						bitmap->RotateFlip(RotateNoneFlipY);
1447 						if (lpgw->color) {
1448 							graphics.DrawImage(bitmap,
1449 								(INT) GPMIN(corners[0].x, corners[1].x),
1450 								(INT) GPMIN(corners[0].y, corners[1].y),
1451 								abs(corners[1].x - corners[0].x),
1452 								abs(corners[1].y - corners[0].y));
1453 						} else {
1454 							/* convert to grayscale */
1455 							ColorMatrix cm = {{{0.30f, 0.30f, 0.30f, 0, 0},
1456 											   {0.59f, 0.59f, 0.59f, 0, 0},
1457 											   {0.11f, 0.11f, 0.11f, 0, 0},
1458 											   {0, 0, 0, 1, 0},
1459 											   {0, 0, 0, 0, 1}
1460 											 }};
1461 							ImageAttributes ia;
1462 							ia.SetColorMatrix(&cm, ColorMatrixFlagsDefault, ColorAdjustTypeBitmap);
1463 							graphics.DrawImage(bitmap,
1464 								RectF((INT) GPMIN(corners[0].x, corners[1].x),
1465 									(INT) GPMIN(corners[0].y, corners[1].y),
1466 									abs(corners[1].x - corners[0].x),
1467 									abs(corners[1].y - corners[0].y)),
1468 								0, 0, width, height,
1469 								UnitPixel, &ia);
1470 						}
1471 						delete bitmap;
1472 					}
1473 					graphics.ResetClip();
1474 					graphics.SetPixelOffsetMode(PixelOffsetModeNone);
1475 				}
1476 				LocalUnlock(curptr->htext);
1477 			}
1478 			seq = (seq + 1) % 6;
1479 			break;
1480 		}
1481 
1482 		default: {
1483 			enum win_pointtypes symbol = (enum win_pointtypes) curptr->op;
1484 
1485 			/* This covers only point symbols. All other codes should be
1486 			   handled in the switch statement. */
1487 			if ((symbol < W_dot) || (symbol > W_last_pointtype))
1488 				break;
1489 
1490 			// draw cached point symbol
1491 			if (ps_caching && (last_symbol == symbol) && (cb != NULL)) {
1492 				// always draw point symbols on integer (pixel) positions
1493 				if (lpgw->oversample)
1494 					graphics.DrawCachedBitmap(cb, INT(xdash + 0.5) - cb_ofs.x, INT(ydash + 0.5) - cb_ofs.y);
1495 				else
1496 					graphics.DrawCachedBitmap(cb, xdash - cb_ofs.x, ydash - cb_ofs.y);
1497 				break;
1498 			} else {
1499 				if (cb != NULL) {
1500 					delete cb;
1501 					cb = NULL;
1502 				}
1503 			}
1504 
1505 			Bitmap *b = 0;
1506 			Graphics *g = 0;
1507 			int xofs;
1508 			int yofs;
1509 
1510 			// Switch between cached and direct drawing
1511 			if (ps_caching) {
1512 				// Create a compatible bitmap
1513 				b = new Bitmap(2 * htic + 3, 2 * vtic + 3, &graphics);
1514 				g = Graphics::FromImage(b);
1515 				if (lpgw->antialiasing)
1516 					g->SetSmoothingMode(SmoothingModeAntiAlias8x8);
1517 				cb_ofs.x = xofs = htic + 1;
1518 				cb_ofs.y = yofs = vtic + 1;
1519 				last_symbol = symbol;
1520 			} else {
1521 				g = &graphics;
1522 				// snap to pixel
1523 				if (lpgw->oversample) {
1524 					xofs = xdash + 0.5;
1525 					yofs = ydash + 0.5;
1526 				} else {
1527 					xofs = xdash;
1528 					yofs = ydash;
1529 				}
1530 			}
1531 
1532 			switch (symbol) {
1533 			case W_dot:
1534 				gdiplusDot(*g, solid_brush, xofs, yofs);
1535 				break;
1536 			case W_plus: /* do plus */
1537 			case W_star: /* do star: first plus, then cross */
1538 				g->DrawLine(&solid_pen, xofs - htic, yofs, xofs + htic, yofs);
1539 				g->DrawLine(&solid_pen, xofs, yofs - vtic, xofs, yofs + vtic);
1540 				if (symbol == W_plus)
1541 					break;
1542 			case W_cross: /* do X */
1543 				g->DrawLine(&solid_pen, xofs - htic, yofs - vtic, xofs + htic - 1, yofs + vtic);
1544 				g->DrawLine(&solid_pen, xofs - htic, yofs + vtic, xofs + htic - 1, yofs - vtic);
1545 				break;
1546 			case W_circle: /* do open circle */
1547 				g->DrawEllipse(&solid_pen, xofs - htic, yofs - htic, 2 * htic, 2 * htic);
1548 				break;
1549 			case W_fcircle: /* do filled circle */
1550 				g->FillEllipse(&solid_brush, xofs - htic, yofs - htic, 2 * htic, 2 * htic);
1551 				break;
1552 			default: {	/* potentially closed figure */
1553 				Point p[6];
1554 				int i;
1555 				int shape = 0;
1556 				int filled = 0;
1557 				int index = 0;
1558 				const float pointshapes[6][10] = {
1559 					{-1, -1, +1, -1, +1, +1, -1, +1, 0, 0}, /* box */
1560 					{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* dummy, circle */
1561 					{ 0, -4./3, -4./3, 2./3,
1562 						4./3,  2./3, 0, 0}, /* triangle */
1563 					{ 0, 4./3, -4./3, -2./3,
1564 						4./3,  -2./3, 0, 0}, /* inverted triangle */
1565 					{ 0, +1, -1,  0,  0, -1, +1,  0, 0, 0}, /* diamond */
1566 					{ 0, 1, 0.95106, 0.30902, 0.58779, -0.80902,
1567 						-0.58779, -0.80902, -0.95106, 0.30902} /* pentagon */
1568 				};
1569 
1570 				// This should never happen since all other codes should be
1571 				// handled in the switch statement.
1572 				if ((symbol < W_box) || (symbol > W_last_pointtype))
1573 					break;
1574 
1575 				// Calculate index, instead of an ugly long switch statement;
1576 				// Depends on definition of commands in wgnuplib.h.
1577 				index = (symbol - W_box);
1578 				shape = index / 2;
1579 				filled = (index % 2) > 0;
1580 
1581 				for (i = 0; i < 5; ++i) {
1582 					if (pointshapes[shape][i * 2 + 1] == 0
1583 						&& pointshapes[shape][i * 2] == 0)
1584 						break;
1585 					p[i].X = xofs + htic * pointshapes[shape][i * 2] + 0.5;
1586 					p[i].Y = yofs + vtic * pointshapes[shape][i * 2 + 1] + 0.5;
1587 				}
1588 				if (filled) {
1589 					/* filled polygon with border */
1590 					g->FillPolygon(&solid_brush, p, i);
1591 				} else {
1592 					/* Outline polygon */
1593 					p[i].X = p[0].X;
1594 					p[i].Y = p[0].Y;
1595 					gdiplusPolyline(*g, solid_pen, p, i + 1);
1596 					gdiplusDot(*g, solid_brush, xofs, yofs);
1597 				}
1598 			} /* default case */
1599 			} /* switch (point symbol) */
1600 
1601 			if (b != NULL) {
1602 				// create a cached bitmap for faster redrawing
1603 				cb = new CachedBitmap(b, &graphics);
1604 				// display point symbol snapped to pixel
1605 				if (lpgw->oversample)
1606 					graphics.DrawCachedBitmap(cb, INT(xdash + 0.5) - xofs, INT(ydash + 0.5) - yofs);
1607 				else
1608 					graphics.DrawCachedBitmap(cb, xdash - xofs, ydash - yofs);
1609 				delete b;
1610 				delete g;
1611 			}
1612 
1613 			if (keysample) {
1614 				draw_update_keybox(lpgw, plotno, xdash + htic, ydash + vtic);
1615 				draw_update_keybox(lpgw, plotno, xdash - htic, ydash - vtic);
1616 			}
1617 			break;
1618 			} /* default case */
1619 		} /* switch(opcode) */
1620 		} /* hide layer? */
1621 
1622 		lastop = curptr->op;
1623 		ngwop++;
1624 		curptr++;
1625 		if ((unsigned)(curptr - blkptr->gwop) >= GWOPMAX) {
1626 			GlobalUnlock(blkptr->hblk);
1627 			blkptr->gwop = (struct GWOP *)NULL;
1628 			if ((blkptr = blkptr->next) == NULL)
1629 				/* If exact multiple of GWOPMAX entries are queued,
1630 				 * next will be NULL. Only the next GraphOp() call would
1631 				 * have allocated a new block */
1632 				break;
1633 			if (!blkptr->gwop)
1634 				blkptr->gwop = (struct GWOP *)GlobalLock(blkptr->hblk);
1635 			if (!blkptr->gwop)
1636 				break;
1637 			curptr = (struct GWOP *)blkptr->gwop;
1638 		}
1639 	} /* while (ngwop < lpgw->nGWOP) */
1640 
1641 	/* clean-up */
1642 	if (pattern_brush)
1643 		delete pattern_brush;
1644 	if (cb)
1645 		delete cb;
1646 	if (font)
1647 		delete font;
1648 	ppt.clear();
1649 }
1650 
1651 
1652 UINT nImageCodecs = 0; // number of image encoders
1653 ImageCodecInfo * pImageCodecInfo = NULL;  // list of image encoders
1654 
1655 
1656 static int
1657 gdiplusGetEncoders()
1658 {
1659 	UINT num = 0;
1660 	UINT size = 0;
1661 
1662 	GetImageEncodersSize(&nImageCodecs, &size);
1663 	if (size == 0)
1664 		return -1;
1665 	pImageCodecInfo = (ImageCodecInfo *) malloc(size);
1666 	if (pImageCodecInfo == NULL)
1667 		return -1;
1668 	GetImageEncoders(nImageCodecs, size, pImageCodecInfo);
1669 	return num;
1670 }
1671 
1672 
1673 void
1674 SaveAsBitmap(LPGW lpgw)
1675 {
1676 	static OPENFILENAMEW Ofn;
1677 	static WCHAR lpstrCustomFilter[256] = { '\0' };
1678 	static WCHAR lpstrFileName[MAX_PATH] = { '\0' };
1679 	static WCHAR lpstrFileTitle[MAX_PATH] = { '\0' };
1680 	UINT i;
1681 
1682 	// make sure GDI+ is initialized
1683 	gdiplusInit();
1684 
1685 	// ask GDI+ about supported encoders
1686 	if (pImageCodecInfo == NULL)
1687 		if (gdiplusGetEncoders() < 0)
1688 			std::cerr << "Error:  GDI+ could not retrieve the list of encoders" << std::endl;
1689 #if 0
1690 	for (i = 0; i < nImageCodecs; i++) {
1691 		char * descr = AnsiText(pImageCodecInfo[i].FormatDescription, S_ENC_DEFAULT);
1692 		char * ext = AnsiText(pImageCodecInfo[i].FilenameExtension, S_ENC_DEFAULT);
1693 		std::cerr << i << ": " << descr << " " << ext << std::endl;
1694 		free(descr);
1695 		free(ext);
1696 	}
1697 #endif
1698 
1699 	// assemble filter list
1700 	UINT npng = 1;
1701 	size_t len;
1702 	for (i = 0, len = 1; i < nImageCodecs; i++) {
1703 		len += wcslen(pImageCodecInfo[i].FormatDescription) + wcslen(pImageCodecInfo[i].FilenameExtension) + 2;
1704 		// make PNG the default selection
1705 		if (wcsnicmp(pImageCodecInfo[i].FormatDescription, L"PNG", 3) == 0)
1706 			npng = i + 1;
1707 	}
1708 	LPWSTR filter = (LPWSTR) malloc(len * sizeof(WCHAR));
1709 	swprintf_s(filter, len, L"%ls\t%ls\t", pImageCodecInfo[0].FormatDescription, pImageCodecInfo[0].FilenameExtension);
1710 	for (i = 1; i < nImageCodecs; i++) {
1711 		size_t len2 = wcslen(pImageCodecInfo[i].FormatDescription) + wcslen(pImageCodecInfo[i].FilenameExtension) + 3;
1712 		LPWSTR type = (LPWSTR) malloc(len2 * sizeof(WCHAR));
1713 		swprintf_s(type, len2, L"%ls\t%ls\t", pImageCodecInfo[i].FormatDescription, pImageCodecInfo[i].FilenameExtension);
1714 		wcscat(filter, type);
1715 		free(type);
1716 	}
1717 	for (i = 1; i < len; i++) {
1718 		if (filter[i] == TEXT('\t'))
1719 			filter[i] = TEXT('\0');
1720 	}
1721 
1722 	// init save file dialog parameters
1723 	Ofn.lStructSize = sizeof(OPENFILENAME);
1724 	Ofn.hwndOwner = lpgw->hWndGraph;
1725 	Ofn.lpstrInitialDir = NULL;
1726 	Ofn.lpstrFilter = filter;
1727 	Ofn.lpstrCustomFilter = lpstrCustomFilter;
1728 	Ofn.nMaxCustFilter = 255;
1729 	Ofn.nFilterIndex = npng;
1730 	Ofn.lpstrFile = lpstrFileName;
1731 	Ofn.nMaxFile = MAX_PATH;
1732 	Ofn.lpstrFileTitle = lpstrFileTitle;
1733 	Ofn.nMaxFileTitle = MAX_PATH;
1734 	Ofn.lpstrInitialDir = NULL;
1735 	Ofn.lpstrTitle = NULL;
1736 	Ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_NOREADONLYRETURN;
1737 	Ofn.lpstrDefExt = L"png";
1738 
1739 	if (GetSaveFileNameW(&Ofn) != 0) {
1740 		// Note that there might be a copy in lpgw->hBitmap., but documentation
1741 		// says we may not use that (although it seems to work).
1742 		// So we get a new copy of the screen:
1743 		HBITMAP hBitmap = GraphGetBitmap(lpgw);
1744 		Bitmap * bitmap = Bitmap::FromHBITMAP(hBitmap, 0);
1745 		UINT ntype = Ofn.nFilterIndex - 1;
1746 #if 0
1747 		LPWSTR wtype = pImageCodecInfo[ntype].FormatDescription;
1748 		char * type = AnsiText(wtype, S_ENC_DEFAULT);
1749 		SizeF size;
1750 		bitmap->GetPhysicalDimension(&size);
1751 		std::cerr << std::endl << "Saving bitmap: size: " << size.Width << " x " << size.Height << "  type: " << type << " ..." << std::endl;
1752 		free(type);
1753 #endif
1754 		bitmap->Save(Ofn.lpstrFile, &(pImageCodecInfo[ntype].Clsid), NULL);
1755 		delete bitmap;
1756 		DeleteObject(hBitmap);
1757 	}
1758 	free(filter);
1759 }
1760 
1761 
1762 static Bitmap *
1763 ResizeBitmap(Bitmap &bmp, INT width, INT height)
1764 {
1765 	unsigned iheight = bmp.GetHeight();
1766 	unsigned iwidth = bmp.GetWidth();
1767 	if (width != height) {
1768 		double aspect = (double)iwidth / iheight;
1769 		if (iwidth > iheight)
1770 			height = static_cast<unsigned>(width / aspect);
1771 		else
1772 			width = static_cast<unsigned>(height * aspect);
1773 	}
1774 	Bitmap * nbmp = new Bitmap(width, height, bmp.GetPixelFormat());
1775 	Graphics graphics(nbmp);
1776 	graphics.DrawImage(&bmp, 0, 0, width - 1, height - 1);
1777 	return nbmp;
1778 }
1779 
1780 
1781 HBITMAP
1782 gdiplusLoadBitmap(LPWSTR file, int size)
1783 {
1784 	gdiplusInit();
1785 	Bitmap ibmp(file);
1786 	Bitmap * bmp = ResizeBitmap(ibmp, size, size);
1787 	HBITMAP hbitmap;
1788 	Color color(0, 0, 0);
1789 	bmp->GetHBITMAP(color, &hbitmap);
1790 	delete bmp;
1791 	return hbitmap;
1792 }
1793