1 /*
2  * draw.c -- drawing routines for XBoard
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * The following terms apply to Digital Equipment Corporation's copyright
12  * interest in XBoard:
13  * ------------------------------------------------------------------------
14  * All Rights Reserved
15  *
16  * Permission to use, copy, modify, and distribute this software and its
17  * documentation for any purpose and without fee is hereby granted,
18  * provided that the above copyright notice appear in all copies and that
19  * both that copyright notice and this permission notice appear in
20  * supporting documentation, and that the name of Digital not be
21  * used in advertising or publicity pertaining to distribution of the
22  * software without specific, written prior permission.
23  *
24  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
25  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
26  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
27  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
28  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
29  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
30  * SOFTWARE.
31  * ------------------------------------------------------------------------
32  *
33  * The following terms apply to the enhanced version of XBoard
34  * distributed by the Free Software Foundation:
35  * ------------------------------------------------------------------------
36  *
37  * GNU XBoard is free software: you can redistribute it and/or modify
38  * it under the terms of the GNU General Public License as published by
39  * the Free Software Foundation, either version 3 of the License, or (at
40  * your option) any later version.
41  *
42  * GNU XBoard is distributed in the hope that it will be useful, but
43  * WITHOUT ANY WARRANTY; without even the implied warranty of
44  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45  * General Public License for more details.
46  *
47  * You should have received a copy of the GNU General Public License
48  * along with this program. If not, see http://www.gnu.org/licenses/.  *
49  *
50  *------------------------------------------------------------------------
51  ** See the file ChangeLog for a revision history.  */
52 
53 #include "config.h"
54 
55 #include <stdio.h>
56 #include <math.h>
57 #include <cairo/cairo.h>
58 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
60 #include <pango/pangocairo.h>
61 
62 #if STDC_HEADERS
63 # include <stdlib.h>
64 # include <string.h>
65 #else /* not STDC_HEADERS */
66 extern char *getenv();
67 # if HAVE_STRING_H
68 #  include <string.h>
69 # else /* not HAVE_STRING_H */
70 #  include <strings.h>
71 # endif /* not HAVE_STRING_H */
72 #endif /* not STDC_HEADERS */
73 
74 #if ENABLE_NLS
75 #include <locale.h>
76 #endif
77 
78 #include "common.h"
79 
80 #include "backend.h"
81 #include "board.h"
82 #include "menus.h"
83 #include "dialogs.h"
84 #include "evalgraph.h"
85 #include "gettext.h"
86 #include "draw.h"
87 
88 
89 #ifdef __EMX__
90 #ifndef HAVE_USLEEP
91 #define HAVE_USLEEP
92 #endif
93 #define usleep(t)   _sleep2(((t)+500)/1000)
94 #endif
95 
96 #ifdef ENABLE_NLS
97 # define  _(s) gettext (s)
98 # define N_(s) gettext_noop (s)
99 #else
100 # define  _(s) (s)
101 # define N_(s)  s
102 #endif
103 
104 #define SOLID 0
105 #define OUTLINE 1
106 Boolean cairoAnimate;
107 Option *currBoard;
108 cairo_surface_t *csBoardWindow;
109 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn];   // png 256 x 256 images
110 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];  // scaled pieces as used
111 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn]; // scaled pieces in store
112 static RsvgHandle *svgPieces[2][(int)BlackPawn]; // vector pieces in store
113 static cairo_surface_t *pngBoardBitmap[2], *pngOriginalBoardBitmap[2];
114 int useTexture, textureW[2], textureH[2];
115 
116 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
117 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
118 
119 #define White(piece) ((int)(piece) < (int)BlackPawn)
120 
121 char svgDir[MSG_SIZ] = SVGDIR;
122 
123 char *crWhite = "#FFFFB0";
124 char *crBlack = "#AD5D3D";
125 
126 struct {
127   int x1, x2, y1, y2;
128 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
129 
130 void
SwitchWindow(int main)131 SwitchWindow (int main)
132 {
133     currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
134 //    CsBoardWindow = DRAWABLE(currBoard);
135 }
136 
137 
138 static void
NewCanvas(Option * graph)139 NewCanvas (Option *graph)
140 {
141 	cairo_t *cr;
142 	int w = graph->max, h = graph->value;
143 	if(graph->choice) cairo_surface_destroy((cairo_surface_t *) graph->choice);
144 	graph->choice = (char**) cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
145 	// paint white, to prevent weirdness when people maximize window and drag pieces over space next to board
146 	cr = cairo_create ((cairo_surface_t *) graph->choice);
147 	cairo_rectangle (cr, 0, 0, w, h);
148 	cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
149 	cairo_fill(cr);
150 	cairo_destroy (cr);
151 	graph->min &= ~REPLACE;
152 }
153 
154 static cairo_surface_t *
CsBoardWindow(Option * opt)155 CsBoardWindow (Option *opt)
156 {   // test before every draw event if we need to resize the canvas
157     if(opt->min & REPLACE) NewCanvas(opt);
158     return DRAWABLE(opt);
159 }
160 
161 
162 void
SelectPieces(VariantClass v)163 SelectPieces(VariantClass v)
164 {
165     int i;
166     for(i=0; i<2; i++) {
167 	int p;
168 	for(p=0; p<=(int)WhiteKing; p++)
169 	   pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
170 	if(v == VariantShogi && BOARD_HEIGHT != 7) { // no exceptions in Tori Shogi
171 	   pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteTokin];
172 	   pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhitePKnight];
173 	   pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhitePLance];
174 	   pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhitePSilver];
175 	   pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
176 	   pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteMonarch]; // for Sho Shogi
177 	}
178 #ifdef GOTHIC
179 	if(v == VariantGothic) {
180 	   pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
181 	}
182 #endif
183 	if(v == VariantSChess) {
184 	   pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
185 	   pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
186 	}
187 	if(v == VariantChuChess) {
188 	   pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
189 	}
190     }
191 }
192 
193 #define BoardSize int
194 void
InitDrawingSizes(BoardSize boardSize,int flags)195 InitDrawingSizes (BoardSize boardSize, int flags)
196 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
197     int boardWidth, boardHeight;
198     static int oldWidth, oldHeight;
199     static VariantClass oldVariant;
200     static int oldTwoBoards = 0, oldNrOfFiles = 0;
201 
202     if(!mainOptions[W_BOARD].handle) return;
203 
204     if(boardSize == -2 && gameInfo.variant != oldVariant
205                        && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
206 	squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
207 	if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
208         squareSize -= lineGap;
209 	CreatePNGPieces(appData.pieceDirectory);
210         CreateGrid();
211     }
212     oldNrOfFiles = BOARD_WIDTH;
213 
214     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
215     oldTwoBoards = twoBoards;
216 
217     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
218     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
219     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
220 
221   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
222 
223     oldWidth = boardWidth; oldHeight = boardHeight;
224     CreateGrid();
225     CreateAnyPieces(0); // redo texture scaling
226 
227     /*
228      * Inhibit shell resizing.
229      */
230     ResizeBoardWindow(boardWidth, boardHeight, 0);
231 
232     DelayedDrag();
233   }
234 
235     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
236     // (only for xpm)
237 
238   if(gameInfo.variant != oldVariant) { // and only if variant changed
239 
240     SelectPieces(gameInfo.variant);
241 
242     oldVariant = gameInfo.variant;
243   }
244   CreateAnimVars();
245 }
246 
247 void
ExposeRedraw(Option * graph,int x,int y,int w,int h)248 ExposeRedraw (Option *graph, int x, int y, int w, int h)
249 {   // copy a selected part of the buffer bitmap to the display
250     cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
251     cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
252     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
253     cairo_rectangle(cr, x, y, w, h);
254     cairo_fill(cr);
255     cairo_destroy(cr);
256 }
257 
258 static int modV[2], modH[2];
259 
260 static void
CreatePNGBoard(char * s,int kind)261 CreatePNGBoard (char *s, int kind)
262 {
263     float w, h;
264     static float n[2] = { 1., 1. };
265     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
266     textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
267     if(strstr(s, ".png")) {
268 	cairo_surface_t *img = cairo_image_surface_create_from_png (s);
269 	if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
270 	    char c, *p = s, *q;
271 	    int r, f;
272 	    if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
273 	    if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
274 	    useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
275 	    w = textureW[kind] = cairo_image_surface_get_width (img);
276 	    h = textureH[kind] = cairo_image_surface_get_height (img);
277 	    transparency[kind] = cairo_image_surface_get_format (img) == CAIRO_FORMAT_ARGB32;
278 	    n[kind] = 1.; modV[kind] = modH[kind] = -1;
279 	    while((q = strchr(p+1, '-'))) p = q; // find last '-'
280 	    if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
281 		if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
282 		textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
283 		textureH[kind] = (h*BOARD_HEIGHT)/r;
284 		n[kind] = (r*squareSize + 0.99)/h;  // scale to make it fit exactly vertically
285 		modV[kind] = r; modH[kind] = f;
286 	    } else
287 	    if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
288 		while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
289 	    } else {
290 		while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
291 	    }
292 	    if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
293 		// create scaled-up copy of the raw png image when it was too small
294 		cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
295 		cairo_t *cr = cairo_create(cs);
296 		pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
297 //		cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
298 		cairo_scale(cr, n[kind], n[kind]);
299 		cairo_set_source_surface (cr, img, 0, 0);
300 		cairo_paint (cr);
301 		cairo_destroy (cr);
302 	    }
303 	}
304     }
305 }
306 
307 char *pngPieceNames[] = // must be in same order as internal piece encoding
308 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
309   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
310   "Sword", "Zebra", "Camel", "Tower", "Wolf", "Hat", "Duck", "Lance", "Dragon", "Gnu", "Cub",
311   "LShield", "Pegasus", "Wizard", "Copper", "Iron", "Viking", "Flag", "Axe", "Dolphin", "Leopard", "Claw",
312   "Left", "Butterfly", "PromoBishop", "PromoRook", "HCrown", "RShield", "Prince", "Phoenix", "Kylin", "Drunk", "Right",
313   "GoldPawn", "GoldKnight", "PromoHorse", "PromoDragon", "GoldLance", "GoldSilver", "HSword", "PromoSword", "PromoHSword", "Princess", "King",
314   NULL
315 };
316 
317 char *backupPiece[] = { // pieces that map on other in default theme ("Crown" - "Drunk")
318   "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
319   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Chancellor", NULL,
320   NULL, "Knight", NULL, "Commoner", NULL, NULL, NULL, "Canon", NULL, NULL, NULL,
321   NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion", "Elephant"
322 };
323 
324 RsvgHandle *
LoadSVG(char * dir,int color,int piece,int retry)325 LoadSVG (char *dir, int color, int piece, int retry)
326 {
327     char buf[MSG_SIZ];
328   RsvgHandle *svg=svgPieces[color][piece];
329   RsvgDimensionData svg_dimensions;
330   GError *svgerror=NULL;
331   cairo_surface_t *img;
332   cairo_t *cr;
333   char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
334 
335     if(!name) return NULL;
336 
337     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
338 
339     if(!svg && *dir) {
340       svg = rsvg_handle_new_from_file(buf, &svgerror);
341       if(!svg) { // failed! If -pid name starts with "sub_" we try to load the piece from the parent directory
342 	char *p = buf, *q;
343 	safeStrCpy(buf, dir, MSG_SIZ);
344 	while((q = strchr(p, '/'))) p = q + 1;
345 	if(!strncmp(p, "sub_", 4)) {
346 	  if(p == buf) safeStrCpy(buf, ".", MSG_SIZ); else p[-1] = NULLCHAR; // strip last directory off path
347 	  return LoadSVG(buf, color, piece, retry);
348 	}
349       }
350       if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
351 	snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
352 	svg = rsvg_handle_new_from_file(buf, &svgerror);
353       }
354     }
355 
356     if(svg) {
357       rsvg_handle_get_dimensions(svg, &svg_dimensions);
358       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
359 
360       cr = cairo_create(img);
361       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
362       rsvg_handle_render_cairo(svg, cr);
363       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
364         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
365         pngPieceImages[color][piece] = img;
366       }
367       cairo_destroy(cr);
368 
369       return svg;
370     }
371     if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
372         return LoadSVG(dir, color, piece, 1);
373     if(svgerror)
374 	g_error_free(svgerror);
375     return NULL;
376 }
377 
378 static void
ScaleOnePiece(int color,int piece,char * pieceDir)379 ScaleOnePiece (int color, int piece, char *pieceDir)
380 {
381   float w, h;
382   char buf[MSG_SIZ];
383   cairo_surface_t *img, *cs;
384   cairo_t *cr;
385 
386   g_type_init ();
387 
388   svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
389 
390   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
391     if(*pieceDir) { // user specified piece directory
392       snprintf(buf, MSG_SIZ, "%s/%s%s.png", pieceDir, color ? "Black" : "White", pngPieceNames[piece]);
393       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
394       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
395 	svgPieces[color][piece] = LoadSVG(pieceDir, color, piece, 0); // so try if he has svg there
396       } else pngPieceImages[color][piece] = img;
397     }
398   }
399 
400   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
401     static int warned = 0;
402     if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
403        && !warned && strcmp(pngPieceNames[piece], "Tile")) {         // but do not complain about missing 'Tile'
404       char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
405       printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
406       DisplayError(msg, 0);
407       warned = 1; // prevent error message being repeated for each piece type
408     }
409   }
410 
411   img = pngPieceImages[color][piece];
412 
413   // create new bitmap to hold scaled piece image (and remove any old)
414   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
415   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
416 
417   if(!img) return;
418 
419   // scaled copying of the raw png image
420   cr = cairo_create(cs);
421   w = cairo_image_surface_get_width (img);
422   h = cairo_image_surface_get_height (img);
423   cairo_scale(cr, squareSize/w, squareSize/h);
424   cairo_set_source_surface (cr, img, 0, 0);
425   cairo_paint (cr);
426   cairo_destroy (cr);
427 
428   if(!appData.trueColors || !*pieceDir) { // operate on bitmap to color it (king-size hack...)
429     int stride = cairo_image_surface_get_stride(cs)/4;
430     int *buf = (int *) cairo_image_surface_get_data(cs);
431     int i, j, p;
432     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
433     cairo_surface_flush(cs);
434     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
435 	int r, a;
436 	float f;
437 	unsigned int c = buf[i*stride + j];
438 	a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
439         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
440 	buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
441 	buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
442 	if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
443 	if(appData.monoMode) {
444 	    if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
445 	    else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
446             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
447 	}
448     }
449     cairo_surface_mark_dirty(cs);
450   }
451 }
452 
453 void
CreatePNGPieces(char * pieceDir)454 CreatePNGPieces (char *pieceDir)
455 {
456   int p;
457   for(p=0; pngPieceNames[p]; p++) {
458     ScaleOnePiece(0, p, pieceDir);
459     ScaleOnePiece(1, p, pieceDir);
460   }
461   SelectPieces(gameInfo.variant);
462 }
463 
464 void
CreateAnyPieces(int p)465 CreateAnyPieces (int p)
466 {   // [HGM] taken out of main
467     if(p) CreatePNGPieces(appData.pieceDirectory);
468     CreatePNGBoard(appData.liteBackTextureFile, 1);
469     CreatePNGBoard(appData.darkBackTextureFile, 0);
470 }
471 
472 static void
ClearPieces()473 ClearPieces ()
474 {
475     int i, p;
476     for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
477 	if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
478 	pngPieceImages[i][p] = NULL;
479 	if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
480 	svgPieces[i][p] = NULL;
481     }
482 }
483 
484 void
InitDrawingParams(int reloadPieces)485 InitDrawingParams (int reloadPieces)
486 {
487     if(reloadPieces) ClearPieces();
488     CreateAnyPieces(1);
489 }
490 
491 void
Preview(int n,char * s)492 Preview (int n, char *s)
493 {
494     static Boolean changed[4];
495     changed[n] = TRUE;
496     switch(n) {
497       case 0: // restore true setting
498 	if(changed[3]) ClearPieces();
499 	CreateAnyPieces(changed[3]); // recomputes textures and (optionally) pieces
500 	for(n=0; n<4; n++) changed[n] = FALSE;
501 	break;
502       case 1:
503       case 2:
504 	CreatePNGBoard(s, n-1);
505 	break;
506       case 3:
507 	ClearPieces();
508 	CreatePNGPieces(s);
509 	break;
510     }
511     DrawPosition(TRUE, NULL);
512 }
513 
514 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
515 
516 float
Color(char * col,int n)517 Color (char *col, int n)
518 {
519   int c;
520   sscanf(col, "#%x", &c);
521   c = c >> 4*n & 255;
522   return c/255.;
523 }
524 
525 void
SetPen(cairo_t * cr,float w,char * col,int dash)526 SetPen (cairo_t *cr, float w, char *col, int dash)
527 {
528   static const double dotted[] = {4.0, 4.0};
529   static int len  = sizeof(dotted) / sizeof(dotted[0]);
530   cairo_set_line_width (cr, w);
531   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
532   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
533 }
534 
DrawSeekAxis(int x,int y,int xTo,int yTo)535 void DrawSeekAxis( int x, int y, int xTo, int yTo )
536 {
537     cairo_t *cr;
538 
539     /* get a cairo_t */
540     cr = cairo_create (CsBoardWindow(currBoard));
541 
542     cairo_move_to (cr, x, y);
543     cairo_line_to(cr, xTo, yTo );
544 
545     SetPen(cr, 2, "#000000", 0);
546     cairo_stroke(cr);
547 
548     /* free memory */
549     cairo_destroy (cr);
550     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
551 }
552 
DrawSeekBackground(int left,int top,int right,int bottom)553 void DrawSeekBackground( int left, int top, int right, int bottom )
554 {
555     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
556 
557     cairo_rectangle (cr, left, top, right-left, bottom-top);
558 
559     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
560     cairo_fill(cr);
561 
562     /* free memory */
563     cairo_destroy (cr);
564     GraphExpose(currBoard, left, top, right-left, bottom-top);
565 }
566 
DrawSeekText(char * buf,int x,int y)567 void DrawSeekText(char *buf, int x, int y)
568 {
569     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
570 
571     cairo_select_font_face (cr, "Sans",
572 			    CAIRO_FONT_SLANT_NORMAL,
573 			    CAIRO_FONT_WEIGHT_NORMAL);
574 
575     cairo_set_font_size (cr, 12.0);
576 
577     cairo_move_to (cr, x, y+4);
578     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
579     cairo_show_text( cr, buf);
580 
581     /* free memory */
582     cairo_destroy (cr);
583     GraphExpose(currBoard, x-5, y-10, 60, 15);
584 }
585 
DrawSeekDot(int x,int y,int colorNr)586 void DrawSeekDot(int x, int y, int colorNr)
587 {
588     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
589     int square = colorNr & 0x80;
590     colorNr &= 0x7F;
591 
592     if(square)
593 	cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
594     else
595 	cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
596 
597     SetPen(cr, 2, "#000000", 0);
598     cairo_stroke_preserve(cr);
599     switch (colorNr) {
600       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0);	break;
601       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
602       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
603     }
604     cairo_fill(cr);
605 
606     /* free memory */
607     cairo_destroy (cr);
608     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
609 }
610 
611 void
InitDrawingHandle(Option * opt)612 InitDrawingHandle (Option *opt)
613 {
614 //    CsBoardWindow = DRAWABLE(opt);
615     currBoard = opt;
616 }
617 
618 void
CreateGrid()619 CreateGrid ()
620 {
621     int i, j;
622 
623     if (lineGap == 0) return;
624 
625     /* [HR] Split this into 2 loops for non-square boards. */
626 
627     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
628         gridSegments[i].x1 = 0;
629         gridSegments[i].x2 =
630           lineGap + BOARD_WIDTH * (squareSize + lineGap);
631         gridSegments[i].y1 = gridSegments[i].y2
632           = lineGap / 2 + (i * (squareSize + lineGap));
633     }
634 
635     for (j = 0; j < BOARD_WIDTH + 1; j++) {
636         gridSegments[j + i].y1 = 0;
637         gridSegments[j + i].y2 =
638           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
639         gridSegments[j + i].x1 = gridSegments[j + i].x2
640           = lineGap / 2 + (j * (squareSize + lineGap));
641     }
642 }
643 
644 void
DrawGrid()645 DrawGrid()
646 {
647   /* draws a grid starting around Nx, Ny squares starting at x,y */
648   int i;
649   float odd = (lineGap & 1)/2.;
650   cairo_t *cr;
651 
652   /* get a cairo_t */
653   cr = cairo_create (CsBoardWindow(currBoard));
654 
655   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
656   SetPen(cr, lineGap, "#000000", 0);
657 
658   /* lines in X */
659   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
660     {
661       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
662       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
663       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
664       cairo_stroke (cr);
665     }
666 
667   /* free memory */
668   cairo_destroy (cr);
669 
670   return;
671 }
672 
673 void
DrawBorder(int x,int y,int type,int odd)674 DrawBorder (int x, int y, int type, int odd)
675 {
676     cairo_t *cr;
677     char *col;
678 
679     switch(type) {
680 	case 0: col = "#000000"; break;
681 	case 1: col = appData.highlightSquareColor; break;
682 	case 2: col = appData.premoveHighlightColor; break;
683 	default: col = "#808080"; break; // cannot happen
684     }
685     cr = cairo_create(CsBoardWindow(currBoard));
686     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
687     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
688     SetPen(cr, lineGap, col, 0);
689     cairo_stroke(cr);
690     cairo_destroy(cr);
691 //    GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
692 }
693 
694 static int
CutOutSquare(int x,int y,int * x0,int * y0,int kind)695 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
696 {
697     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
698     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
699     *x0 = 0; *y0 = 0;
700     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
701     if(modV[kind] > 0) nx %= modH[kind], ny %= modV[kind]; // tile fixed-format board periodically to extend it
702     if(textureW[kind] < W*squareSize)
703 	*x0 = (textureW[kind] - squareSize) * nx/(W-1);
704     else
705 	*x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
706     if(textureH[kind] < H*squareSize)
707 	*y0 = (textureH[kind] - squareSize) * ny/(H-1);
708     else
709 	*y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
710     return 1;
711 }
712 
713 void
DrawLogo(Option * opt,void * logo)714 DrawLogo (Option *opt, void *logo)
715 {
716     cairo_surface_t *img;
717     cairo_t *cr;
718     int w, h;
719 
720     if(!opt) return;
721     cr = cairo_create(CsBoardWindow(opt));
722     cairo_rectangle (cr, 0, 0, opt->max, opt->value);
723     cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
724     cairo_fill(cr); // paint background in case logo does not exist
725     if(logo) {
726         img = cairo_image_surface_create_from_png (logo);
727         if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
728 	    w = cairo_image_surface_get_width (img);
729 	    h = cairo_image_surface_get_height (img);
730 //        cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
731 	    cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
732 	    cairo_set_source_surface (cr, img, 0, 0);
733 	    cairo_paint (cr);
734         }
735 	cairo_surface_destroy (img);
736     }
737     cairo_destroy (cr);
738     GraphExpose(opt, 0, 0, opt->max, opt->value);
739 }
740 
741 static void
BlankSquare(cairo_surface_t * dest,int x,int y,int color,ChessSquare piece,int fac)742 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
743 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
744     int x0, y0, texture = (useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color);
745     cairo_t *cr;
746 
747     cr = cairo_create (dest);
748 
749     if(!texture || transparency[color]) // draw color also (as background) when texture could be transparent
750     { // evenly colored squares
751 	char *col = NULL;
752 	switch (color) {
753 	  case 0: col = appData.darkSquareColor; break;
754 	  case 1: col = appData.lightSquareColor; break;
755 	  case 2: col = "#000000"; break;
756 	  default: col = "#808080"; break; // cannot happen
757 	}
758 	SetPen(cr, 2.0, col, 0);
759 	cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
760 	cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
761 	cairo_fill (cr);
762     }
763     if (texture) {
764 	    cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
765 	    cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
766 	    cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
767 	    cairo_fill (cr);
768     }
769     cairo_destroy (cr);
770 }
771 
772 static void
pngDrawPiece(cairo_surface_t * dest,ChessSquare piece,int square_color,int x,int y)773 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
774 {
775     int kind;
776     cairo_t *cr;
777 
778     if ((int)piece < (int) BlackPawn) {
779 	kind = 0;
780     } else {
781 	kind = 1;
782 	piece -= BlackPawn;
783     }
784     if(piece == WhiteKing && kind == appData.jewelled) piece = WhiteZebra;
785     if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
786     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
787     cr = cairo_create (dest);
788     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
789     cairo_paint(cr);
790     cairo_destroy (cr);
791 }
792 
793 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
794 
795 void
DoDrawDot(cairo_surface_t * cs,int marker,int x,int y,int r)796 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
797 {
798 	cairo_t *cr;
799 
800 	cr = cairo_create(cs);
801 	cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
802 	if(appData.monoMode) {
803 	    SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
804 	    cairo_stroke_preserve(cr);
805 	    SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
806 	} else {
807 	    SetPen(cr, 2, markerColor[marker-1], 0);
808 	}
809 	cairo_fill(cr);
810 
811 	cairo_destroy(cr);
812 }
813 
814 void
DrawDot(int marker,int x,int y,int r)815 DrawDot (int marker, int x, int y, int r)
816 { // used for atomic captures; no need to draw on backup
817   DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
818   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
819 }
820 
821 static void
DrawUnicode(cairo_surface_t * canvas,char * string,int x,int y,char id,int flip,int size,int vpos)822 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip, int size, int vpos)
823 {
824 //	cairo_text_extents_t te;
825 	cairo_t *cr;
826 	int s = 1 - 2*flip;
827 	PangoLayout *layout;
828 	PangoFontDescription *desc;
829 	PangoRectangle r;
830 	char fontName[MSG_SIZ];
831 
832 	cr = cairo_create (canvas);
833 	layout = pango_cairo_create_layout(cr);
834 	pango_layout_set_text(layout, string, -1);
835 	snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", size*squareSize/64);
836 	desc = pango_font_description_from_string(fontName);
837 	pango_layout_set_font_description(layout, desc);
838 	pango_font_description_free(desc);
839         pango_layout_get_pixel_extents(layout, NULL, &r);
840 	cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (32+vpos*s)*squareSize/64 - s*r.height/2);
841 	if(s < 0) cairo_rotate(cr, G_PI);
842 	cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
843 	pango_cairo_update_layout(cr, layout);
844 	pango_cairo_show_layout(cr, layout);
845 	g_object_unref(layout);
846 	cairo_destroy(cr);
847 }
848 
849 void
DrawText(char * string,int x,int y,int align)850 DrawText (char *string, int x, int y, int align)
851 {
852 	int xx = x, yy = y;
853 	cairo_text_extents_t te;
854 	cairo_t *cr;
855 
856 	cr = cairo_create (CsBoardWindow(currBoard));
857 	cairo_select_font_face (cr, "Sans",
858 		    CAIRO_FONT_SLANT_NORMAL,
859 		    CAIRO_FONT_WEIGHT_BOLD);
860 
861 	cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
862 	// calculate where it goes
863 	cairo_text_extents (cr, string, &te);
864 
865 	if (align == 1) {
866 	    xx += squareSize - te.width - te.x_bearing - 1;
867 	    yy += squareSize - te.height - te.y_bearing - 1;
868 	} else if (align == 2) {
869 	    xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
870 	} else if (align == 3) {
871 	    xx += squareSize - te.width -te.x_bearing - 1;
872 	    yy += -te.y_bearing + 3;
873 	} else if (align == 4) {
874 	    xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
875 	}
876 
877 	cairo_move_to (cr, xx-1, yy);
878 	if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
879 	else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
880 	cairo_show_text (cr, string);
881 	cairo_destroy (cr);
882 }
883 
884 void
InscribeKanji(cairo_surface_t * canvas,ChessSquare piece,int x,int y)885 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
886 {
887     char *p, *q, buf[20], nr = 1;
888     int i, n, size = 40, flip = appData.upsideDown && flipView == (piece < BlackPawn);
889     if(piece == EmptySquare) return;
890     if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
891     p = appData.inscriptions;
892     if(*p > '0' && *p < '3') nr = *p++ - '0'; // nr of kanji per piece
893     n = piece; i = 0;
894     while(piece > WhitePawn) {
895       if(*p == '/') p++, piece = n - WhitePBishop; // secondary series
896       if(*p++ == NULLCHAR) {
897         if(n != WhiteKing) return;
898         p = q;
899         break;
900       }
901       q = p - 1;
902       while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
903       if(*q != '.' && ++i < nr) continue; // yet more kanji for the current piece
904       piece--; i = 0;
905     }
906     strncpy(buf, p, 20);
907     for(q=buf; (*++q & 0xC0) == 0x80;); // skip first unicode
908     if(nr > 1) {
909       p = q;
910       while((*++p & 0xC0) == 0x80) {} // skip second unicode
911       *p = NULLCHAR; size = 30; i = 16;
912       DrawUnicode(canvas, q, x, y, PieceToChar(n), flip, size, -10);
913     } else i = 4;
914     *q = NULLCHAR;
915     DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip, size, i);
916 }
917 
918 void
DrawOneSquare(int x,int y,ChessSquare piece,int square_color,int marker,char * tString,char * bString,int align)919 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
920 {   // basic front-end board-draw function: takes care of everything that can be in square:
921     // piece, background, coordinate/count, marker dot
922 
923     if (piece == EmptySquare) {
924 	BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
925     } else {
926 	pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
927         if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
928     }
929 
930     if(align) { // square carries inscription (coord or piece count)
931 	if(align > 1) DrawText(tString, x, y, align);       // top (rank or count)
932 	if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
933     }
934 
935     if(marker) { // print fat marker dot, if requested
936 	DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
937     }
938 }
939 
940 /****	Animation code by Hugh Fisher, DCS, ANU. ****/
941 
942 /*	Masks for XPM pieces. Black and white pieces can have
943 	different shapes, but in the interest of retaining my
944 	sanity pieces must have the same outline on both light
945 	and dark squares, and all pieces must use the same
946 	background square colors/images.		*/
947 
948 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
949 
950 static void
InitAnimState(AnimNr anr)951 InitAnimState (AnimNr anr)
952 {
953     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
954     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
955     c_animBufs[anr+4] = CsBoardWindow(currBoard);
956     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
957     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
958 }
959 
960 void
CreateAnimVars()961 CreateAnimVars ()
962 {
963   InitAnimState(Game);
964   InitAnimState(Player);
965 }
966 
967 static void
CairoOverlayPiece(ChessSquare piece,cairo_surface_t * dest)968 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
969 {
970   static cairo_t *pieceSource;
971   pieceSource = cairo_create (dest);
972   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
973   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
974   else cairo_paint(pieceSource);
975   cairo_destroy (pieceSource);
976   if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
977 }
978 
979 void
InsertPiece(AnimNr anr,ChessSquare piece)980 InsertPiece (AnimNr anr, ChessSquare piece)
981 {
982     CairoOverlayPiece(piece, c_animBufs[anr]);
983 }
984 
985 void
DrawBlank(AnimNr anr,int x,int y,int startColor)986 DrawBlank (AnimNr anr, int x, int y, int startColor)
987 {
988     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
989 }
990 
CopyRectangle(AnimNr anr,int srcBuf,int destBuf,int srcX,int srcY,int width,int height,int destX,int destY)991 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
992 		 int srcX, int srcY, int width, int height, int destX, int destY)
993 {
994 	cairo_t *cr;
995 	c_animBufs[anr+4] = CsBoardWindow(currBoard);
996 	cr = cairo_create (c_animBufs[anr+destBuf]);
997 	cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
998 	cairo_rectangle (cr, destX, destY, width, height);
999 	cairo_fill (cr);
1000 	cairo_destroy (cr);
1001 	if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
1002 	    GraphExpose(currBoard, destX, destY, width, height);
1003 }
1004 
1005 void
SetDragPiece(AnimNr anr,ChessSquare piece)1006 SetDragPiece (AnimNr anr, ChessSquare piece)
1007 {
1008 }
1009 
1010 /* [AS] Arrow highlighting support */
1011 
1012 void
DoDrawPolygon(cairo_surface_t * cs,Pnt arrow[],int nr)1013 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
1014 {
1015     cairo_t *cr;
1016     int i;
1017     cr = cairo_create (cs);
1018     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
1019     for (i=0;i<nr;i++) {
1020         cairo_line_to(cr, arrow[i].x, arrow[i].y);
1021     }
1022     if(appData.monoMode) { // should we always outline arrow?
1023         cairo_line_to(cr, arrow[0].x, arrow[0].y);
1024         SetPen(cr, 2, "#000000", 0);
1025         cairo_stroke_preserve(cr);
1026     }
1027     SetPen(cr, 2, appData.highlightSquareColor, 0);
1028     cairo_fill(cr);
1029 
1030     /* free memory */
1031     cairo_destroy (cr);
1032 }
1033 
1034 void
DrawPolygon(Pnt arrow[],int nr)1035 DrawPolygon (Pnt arrow[], int nr)
1036 {
1037     DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1038 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1039 }
1040 
1041 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1042 
1043 static void
ChoosePen(cairo_t * cr,int i)1044 ChoosePen(cairo_t *cr, int i)
1045 {
1046   switch(i) {
1047     case PEN_BLACK:
1048       SetPen(cr, 1.0, "#000000", 0);
1049       break;
1050     case PEN_DOTTED:
1051       SetPen(cr, 1.0, "#A0A0A0", 1);
1052       break;
1053     case PEN_BLUEDOTTED:
1054       SetPen(cr, 1.0, "#0000FF", 1);
1055       break;
1056     case PEN_BOLDWHITE:
1057       SetPen(cr, 3.0, crWhite, 0);
1058       break;
1059     case PEN_BOLDBLACK:
1060       SetPen(cr, 3.0, crBlack, 0);
1061       break;
1062     case PEN_BACKGD:
1063       SetPen(cr, 3.0, "#E0E0F0", 0);
1064       break;
1065   }
1066 }
1067 
1068 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1069 void
DrawSegment(int x,int y,int * lastX,int * lastY,int penType)1070 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1071 {
1072   static int curX, curY;
1073 
1074   if(penType != PEN_NONE) {
1075     cairo_t *cr = cairo_create(CsBoardWindow(disp));
1076     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1077     cairo_move_to (cr, curX, curY);
1078     cairo_line_to (cr, x,y);
1079     ChoosePen(cr, penType);
1080     cairo_stroke (cr);
1081     cairo_destroy (cr);
1082   }
1083 
1084   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1085   curX = x; curY = y;
1086 }
1087 
1088 // front-end wrapper for drawing functions to do rectangles
1089 void
DrawRectangle(int left,int top,int right,int bottom,int side,int style)1090 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1091 {
1092   cairo_t *cr;
1093 
1094   cr = cairo_create (CsBoardWindow(disp));
1095   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1096   cairo_rectangle (cr, left, top, right-left, bottom-top);
1097   switch(side)
1098     {
1099     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1100     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1101     case 2: ChoosePen(cr, PEN_BACKGD); break;
1102     }
1103   cairo_fill (cr);
1104 
1105   if(style != FILLED)
1106     {
1107       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1108       ChoosePen(cr, PEN_BLACK);
1109       cairo_stroke (cr);
1110     }
1111 
1112   cairo_destroy(cr);
1113 }
1114 
1115 // front-end wrapper for putting text in graph
1116 void
DrawEvalText(char * buf,int cbBuf,int y)1117 DrawEvalText (char *buf, int cbBuf, int y)
1118 {
1119     // the magic constants 8 and 5 should really be derived from the font size somehow
1120   cairo_text_extents_t extents;
1121   cairo_t *cr = cairo_create(CsBoardWindow(disp));
1122 
1123   /* GTK-TODO this has to go into the font-selection */
1124   cairo_select_font_face (cr, "Sans",
1125 			  CAIRO_FONT_SLANT_NORMAL,
1126 			  CAIRO_FONT_WEIGHT_NORMAL);
1127   cairo_set_font_size (cr, 12.0);
1128 
1129 
1130   cairo_text_extents (cr, buf, &extents);
1131 
1132   cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1133   cairo_text_path (cr, buf);
1134   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1135   cairo_fill_preserve (cr);
1136   cairo_set_source_rgb (cr, 0, 1.0, 0);
1137   cairo_set_line_width (cr, 0.1);
1138   cairo_stroke (cr);
1139 
1140   /* free memory */
1141   cairo_destroy (cr);
1142 }
1143