Fix Seirawan gating at Rook square in PGN castling moves
[xboard.git] / draw.c
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
131 SwitchWindow (int main)
132 {
133     currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
134 //    CsBoardWindow = DRAWABLE(currBoard);
135 }
136
137
138 static void
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 *
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
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
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
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
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 *
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 Wint(FILE *f, int n)
379 {   // write 32-bit int, lsb first
380     fprintf(f, "%c%c%c%c", n&255, n>>8&255, n>>16&255, n>>24&255);
381 }
382
383 static void
384 SaveWindowsBitmap(ChessSquare piece, int color, int *data, int stride, int w, int h, int bpp)
385 {
386     int i, v, line = (w*bpp + 3)>>2, size = line*4*h;
387     char buf[100];
388     FILE *f;
389     snprintf(buf, 100, "bmaps/piece%d_%d%c.bmp", piece, w, color ? 's': 'o');
390     if(piece >= appData.bmpSave && piece != WhiteKing) return;
391     f = fopen(buf, "wb");
392     if(!f) return;
393     fprintf(f, "BM");
394     Wint(f, size+54); Wint(f, 0); Wint(f, 54); Wint(f, 40); // file size, reserved, image offset, header size
395     Wint(f, w); Wint(f, h); Wint(f, 8*bpp << 16 | 1); // width, height, planes & bits/pix
396     Wint(f, 0); // compression
397     Wint(f, size); Wint(f, 3780); Wint(f, 3780); Wint(f, 0); Wint(f, 0); // image size, pix/m (H and V), color-table size (2x)
398     for(v=h-1; v>=0; v--) {
399         for(i=0; i<w; i++) {
400             int pix = data[stride*v+i], r=pix>>16&255, g=pix>>8&255, b=pix&255, a=pix>>24&255;
401             r = r*a/255; b = b*a/255; g = g*a/255;
402             if(bpp == 4) fprintf(f, "%c%c%c%c", b, g, r, a); else fprintf(f, "%c%c%c", b, g, r);
403         }
404         i *= bpp; while(i++ < line*4) fprintf(f, "%c", 0); // padding
405     }
406     fclose(f);
407     if(appData.bmpSave && piece == WhiteKing && color) exit(0);
408 }
409
410 static void
411 ScaleOnePiece (int color, int piece, char *pieceDir)
412 {
413   float w, h;
414   char buf[MSG_SIZ];
415   cairo_surface_t *img, *cs;
416   cairo_t *cr;
417
418   g_type_init ();
419
420   svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
421
422   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
423     if(*pieceDir) { // user specified piece directory
424       snprintf(buf, MSG_SIZ, "%s/%s%s.png", pieceDir, color ? "Black" : "White", pngPieceNames[piece]);
425       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
426       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
427         svgPieces[color][piece] = LoadSVG(pieceDir, color, piece, 0); // so try if he has svg there
428       } else pngPieceImages[color][piece] = img;
429     }
430   }
431
432   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
433     static int warned = 0;
434     if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg 
435        && !warned && strcmp(pngPieceNames[piece], "Tile")) {         // but do not complain about missing 'Tile'
436       char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
437       printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
438       DisplayError(msg, 0);
439       warned = 1; // prevent error message being repeated for each piece type
440     }
441   }
442
443   img = pngPieceImages[color][piece];
444
445   // create new bitmap to hold scaled piece image (and remove any old)
446   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
447   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
448
449   if(!img) return;
450
451   // scaled copying of the raw png image
452   cr = cairo_create(cs);
453   w = cairo_image_surface_get_width (img);
454   h = cairo_image_surface_get_height (img);
455   cairo_scale(cr, squareSize/w, squareSize/h);
456   cairo_set_source_surface (cr, img, 0, 0);
457   cairo_paint (cr);
458   cairo_destroy (cr);
459
460   if(!appData.trueColors || !*pieceDir) { // operate on bitmap to color it (king-size hack...)
461     int stride = cairo_image_surface_get_stride(cs)/4;
462     int *buf = (int *) cairo_image_surface_get_data(cs);
463     int i, j, p;
464     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
465     cairo_surface_flush(cs);
466     SaveWindowsBitmap(piece, color, buf, stride, squareSize, squareSize, 4);
467     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
468         int r, a;
469         float f;
470         unsigned int c = buf[i*stride + j];
471         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
472         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
473         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
474         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
475         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
476         if(appData.monoMode) {
477             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
478             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
479             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
480         }
481     }
482     cairo_surface_mark_dirty(cs);
483   }
484 }
485
486 void
487 CreatePNGPieces (char *pieceDir)
488 {
489   int p;
490   for(p=0; pngPieceNames[p]; p++) {
491     ScaleOnePiece(0, p, pieceDir);
492     ScaleOnePiece(1, p, pieceDir);
493   }
494   SelectPieces(gameInfo.variant);
495 }
496
497 void
498 CreateAnyPieces (int p)
499 {   // [HGM] taken out of main
500     if(p) CreatePNGPieces(appData.pieceDirectory);
501     CreatePNGBoard(appData.liteBackTextureFile, 1);
502     CreatePNGBoard(appData.darkBackTextureFile, 0);
503 }
504
505 static void
506 ClearPieces ()
507 {
508     int i, p;
509     for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
510         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
511         pngPieceImages[i][p] = NULL;
512         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
513         svgPieces[i][p] = NULL;
514     }
515 }
516
517 void
518 InitDrawingParams (int reloadPieces)
519 {
520     if(reloadPieces) ClearPieces();
521     CreateAnyPieces(1);
522 }
523
524 void
525 Preview (int n, char *s)
526 {
527     static Boolean changed[4];
528     changed[n] = TRUE;
529     switch(n) {
530       case 0: // restore true setting
531         if(changed[3]) ClearPieces();
532         CreateAnyPieces(changed[3]); // recomputes textures and (optionally) pieces
533         for(n=0; n<4; n++) changed[n] = FALSE;
534         break;
535       case 1: 
536       case 2:
537         CreatePNGBoard(s, n-1);
538         break;
539       case 3:
540         ClearPieces();
541         CreatePNGPieces(s);
542         break;
543     }
544     DrawPosition(TRUE, NULL);
545 }
546
547 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
548
549 float
550 Color (char *col, int n)
551 {
552   int c;
553   sscanf(col, "#%x", &c);
554   c = c >> 4*n & 255;
555   return c/255.;
556 }
557
558 void
559 SetPen (cairo_t *cr, float w, char *col, int dash)
560 {
561   static const double dotted[] = {4.0, 4.0};
562   static int len  = sizeof(dotted) / sizeof(dotted[0]);
563   cairo_set_line_width (cr, w);
564   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
565   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
566 }
567
568 void DrawSeekAxis( int x, int y, int xTo, int yTo )
569 {
570     cairo_t *cr;
571
572     /* get a cairo_t */
573     cr = cairo_create (CsBoardWindow(currBoard));
574
575     cairo_move_to (cr, x, y);
576     cairo_line_to(cr, xTo, yTo );
577
578     SetPen(cr, 2, "#000000", 0);
579     cairo_stroke(cr);
580
581     /* free memory */
582     cairo_destroy (cr);
583     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
584 }
585
586 void DrawSeekBackground( int left, int top, int right, int bottom )
587 {
588     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
589
590     cairo_rectangle (cr, left, top, right-left, bottom-top);
591
592     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
593     cairo_fill(cr);
594
595     /* free memory */
596     cairo_destroy (cr);
597     GraphExpose(currBoard, left, top, right-left, bottom-top);
598 }
599
600 void DrawSeekText(char *buf, int x, int y)
601 {
602     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
603
604     cairo_select_font_face (cr, "Sans",
605                             CAIRO_FONT_SLANT_NORMAL,
606                             CAIRO_FONT_WEIGHT_NORMAL);
607
608     cairo_set_font_size (cr, 12.0);
609
610     cairo_move_to (cr, x, y+4);
611     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
612     cairo_show_text( cr, buf);
613
614     /* free memory */
615     cairo_destroy (cr);
616     GraphExpose(currBoard, x-5, y-10, 60, 15);
617 }
618
619 void DrawSeekDot(int x, int y, int colorNr)
620 {
621     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
622     int square = colorNr & 0x80;
623     colorNr &= 0x7F;
624
625     if(square)
626         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
627     else
628         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
629
630     SetPen(cr, 2, "#000000", 0);
631     cairo_stroke_preserve(cr);
632     switch (colorNr) {
633       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
634       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
635       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
636     }
637     cairo_fill(cr);
638
639     /* free memory */
640     cairo_destroy (cr);
641     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
642 }
643
644 void
645 InitDrawingHandle (Option *opt)
646 {
647 //    CsBoardWindow = DRAWABLE(opt);
648     currBoard = opt;
649 }
650
651 void
652 CreateGrid ()
653 {
654     int i, j;
655
656     if (lineGap == 0) return;
657
658     /* [HR] Split this into 2 loops for non-square boards. */
659
660     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
661         gridSegments[i].x1 = 0;
662         gridSegments[i].x2 =
663           lineGap + BOARD_WIDTH * (squareSize + lineGap);
664         gridSegments[i].y1 = gridSegments[i].y2
665           = lineGap / 2 + (i * (squareSize + lineGap));
666     }
667
668     for (j = 0; j < BOARD_WIDTH + 1; j++) {
669         gridSegments[j + i].y1 = 0;
670         gridSegments[j + i].y2 =
671           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
672         gridSegments[j + i].x1 = gridSegments[j + i].x2
673           = lineGap / 2 + (j * (squareSize + lineGap));
674     }
675 }
676
677 void
678 DrawGrid()
679 {
680   /* draws a grid starting around Nx, Ny squares starting at x,y */
681   int i;
682   float odd = (lineGap & 1)/2.;
683   cairo_t *cr;
684
685   /* get a cairo_t */
686   cr = cairo_create (CsBoardWindow(currBoard));
687
688   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
689   SetPen(cr, lineGap, "#000000", 0);
690
691   /* lines in X */
692   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
693     {
694       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
695       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
696       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
697       cairo_stroke (cr);
698     }
699
700   /* free memory */
701   cairo_destroy (cr);
702
703   return;
704 }
705
706 void
707 DrawBorder (int x, int y, int type, int odd)
708 {
709     cairo_t *cr;
710     char *col;
711
712     switch(type) {
713         case 0: col = "#000000"; break;
714         case 1: col = appData.highlightSquareColor; break;
715         case 2: col = appData.premoveHighlightColor; break;
716         default: col = "#808080"; break; // cannot happen
717     }
718     cr = cairo_create(CsBoardWindow(currBoard));
719     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
720     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
721     SetPen(cr, lineGap, col, 0);
722     cairo_stroke(cr);
723     cairo_destroy(cr);
724 //    GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
725 }
726
727 static int
728 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
729 {
730     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
731     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
732     *x0 = 0; *y0 = 0;
733     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
734     if(modV[kind] > 0) nx %= modH[kind], ny %= modV[kind]; // tile fixed-format board periodically to extend it
735     if(textureW[kind] < W*squareSize)
736         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
737     else
738         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
739     if(textureH[kind] < H*squareSize)
740         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
741     else
742         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
743     return 1;
744 }
745
746 void
747 DrawLogo (Option *opt, void *logo)
748 {
749     cairo_surface_t *img;
750     cairo_t *cr;
751     int w, h;
752
753     if(!opt) return;
754     cr = cairo_create(CsBoardWindow(opt));
755     cairo_rectangle (cr, 0, 0, opt->max, opt->value);
756     cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
757     cairo_fill(cr); // paint background in case logo does not exist
758     if(logo) {
759         img = cairo_image_surface_create_from_png (logo);
760         if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
761             w = cairo_image_surface_get_width (img);
762             h = cairo_image_surface_get_height (img);
763 //        cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
764             cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
765             cairo_set_source_surface (cr, img, 0, 0);
766             cairo_paint (cr);
767         }
768         cairo_surface_destroy (img);
769     }
770     cairo_destroy (cr);
771     GraphExpose(opt, 0, 0, opt->max, opt->value);
772 }
773
774 static void
775 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
776 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
777     int x0, y0, texture = (useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color);
778     cairo_t *cr;
779
780     cr = cairo_create (dest);
781
782     if(!texture || transparency[color]) // draw color also (as background) when texture could be transparent
783     { // evenly colored squares
784         char *col = NULL;
785         switch (color) {
786           case 0: col = appData.darkSquareColor; break;
787           case 1: col = appData.lightSquareColor; break;
788           case 2: col = "#000000"; break;
789           default: col = "#808080"; break; // cannot happen
790         }
791         SetPen(cr, 2.0, col, 0);
792         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
793         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
794         cairo_fill (cr);
795     }
796     if (texture) {
797             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
798             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
799             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
800             cairo_fill (cr);
801     }
802     cairo_destroy (cr);
803 }
804
805 static void
806 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
807 {
808     int kind;
809     cairo_t *cr;
810
811     if ((int)piece < (int) BlackPawn) {
812         kind = 0;
813     } else {
814         kind = 1;
815         piece -= BlackPawn;
816     }
817     if(piece == WhiteKing && kind == appData.jewelled) piece = WhiteZebra;
818     if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
819     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
820     cr = cairo_create (dest);
821     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
822     cairo_paint(cr);
823     cairo_destroy (cr);
824 }
825
826 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
827
828 void
829 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
830 {
831         cairo_t *cr;
832
833         cr = cairo_create(cs);
834         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
835         if(appData.monoMode) {
836             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
837             cairo_stroke_preserve(cr);
838             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
839         } else {
840             SetPen(cr, 2, markerColor[marker-1], 0);
841         }
842         cairo_fill(cr);
843
844         cairo_destroy(cr);
845 }
846
847 void
848 DrawDot (int marker, int x, int y, int r)
849 { // used for atomic captures; no need to draw on backup
850   DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
851   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
852 }
853
854 static void
855 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip, int size, int vpos)
856 {
857 //      cairo_text_extents_t te;
858         cairo_t *cr;
859         int s = 1 - 2*flip;
860         PangoLayout *layout;
861         PangoFontDescription *desc;
862         PangoRectangle r;
863         char fontName[MSG_SIZ];
864
865         cr = cairo_create (canvas);
866         layout = pango_cairo_create_layout(cr);
867         pango_layout_set_text(layout, string, -1);
868         snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", size*squareSize/64);
869         desc = pango_font_description_from_string(fontName);
870         pango_layout_set_font_description(layout, desc);
871         pango_font_description_free(desc);
872         pango_layout_get_pixel_extents(layout, NULL, &r);
873         cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (32+vpos*s)*squareSize/64 - s*r.height/2);
874         if(s < 0) cairo_rotate(cr, G_PI);
875         cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
876         pango_cairo_update_layout(cr, layout);
877         pango_cairo_show_layout(cr, layout);
878         g_object_unref(layout);
879         cairo_destroy(cr);
880 }
881
882 void
883 DrawText (char *string, int x, int y, int align)
884 {
885         int xx = x, yy = y;
886         cairo_text_extents_t te;
887         cairo_t *cr;
888
889         cr = cairo_create (CsBoardWindow(currBoard));
890         cairo_select_font_face (cr, "Sans",
891                     CAIRO_FONT_SLANT_NORMAL,
892                     CAIRO_FONT_WEIGHT_BOLD);
893
894         cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
895         // calculate where it goes
896         cairo_text_extents (cr, string, &te);
897
898         if (align == 1) {
899             xx += squareSize - te.width - te.x_bearing - 1;
900             yy += squareSize - te.height - te.y_bearing - 1;
901         } else if (align == 2) {
902             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
903         } else if (align == 3) {
904             xx += squareSize - te.width -te.x_bearing - 1;
905             yy += -te.y_bearing + 3;
906         } else if (align == 4) {
907             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
908         }
909
910         cairo_move_to (cr, xx-1, yy);
911         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
912         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
913         cairo_show_text (cr, string);
914         cairo_destroy (cr);
915 }
916
917 void
918 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
919 {
920     char *p, *q, buf[20], nr = 1;
921     int i, n, size = 40, flip = appData.upsideDown && flipView == (piece < BlackPawn);
922     if(piece == EmptySquare) return;
923     if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
924     p = appData.inscriptions;
925     if(*p > '0' && *p < '3') nr = *p++ - '0'; // nr of kanji per piece
926     n = piece; i = 0;
927     while(piece > WhitePawn) {
928       if(*p == '/') p++, piece = n - WhitePBishop; // secondary series
929       if(*p++ == NULLCHAR) {
930         if(n != WhiteKing) return;
931         p = q;
932         break;
933       }
934       q = p - 1;
935       while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
936       if(*q != '.' && ++i < nr) continue; // yet more kanji for the current piece
937       piece--; i = 0;
938     }
939     strncpy(buf, p, 20);
940     for(q=buf; (*++q & 0xC0) == 0x80;); // skip first unicode
941     if(nr > 1) {
942       p = q;
943       while((*++p & 0xC0) == 0x80) {} // skip second unicode
944       *p = NULLCHAR; size = 30; i = 16;
945       DrawUnicode(canvas, q, x, y, PieceToChar(n), flip, size, -10);
946     } else i = 4;
947     *q = NULLCHAR;
948     DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip, size, i);
949 }
950
951 void
952 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
953 {   // basic front-end board-draw function: takes care of everything that can be in square:
954     // piece, background, coordinate/count, marker dot
955
956     if (piece == EmptySquare) {
957         BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
958     } else {
959         pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
960         if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
961     }
962
963     if(align) { // square carries inscription (coord or piece count)
964         if(align > 1) DrawText(tString, x, y, align);       // top (rank or count)
965         if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
966     }
967
968     if(marker) { // print fat marker dot, if requested
969         DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
970     }
971 }
972
973 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
974
975 /*      Masks for XPM pieces. Black and white pieces can have
976         different shapes, but in the interest of retaining my
977         sanity pieces must have the same outline on both light
978         and dark squares, and all pieces must use the same
979         background square colors/images.                */
980
981 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
982
983 static void
984 InitAnimState (AnimNr anr)
985 {
986     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
987     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
988     c_animBufs[anr+4] = CsBoardWindow(currBoard);
989     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
990     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
991 }
992
993 void
994 CreateAnimVars ()
995 {
996   InitAnimState(Game);
997   InitAnimState(Player);
998 }
999
1000 static void
1001 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
1002 {
1003   static cairo_t *pieceSource;
1004   pieceSource = cairo_create (dest);
1005   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
1006   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
1007   else cairo_paint(pieceSource);
1008   cairo_destroy (pieceSource);
1009   if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
1010 }
1011
1012 void
1013 InsertPiece (AnimNr anr, ChessSquare piece)
1014 {
1015     CairoOverlayPiece(piece, c_animBufs[anr]);
1016 }
1017
1018 void
1019 DrawBlank (AnimNr anr, int x, int y, int startColor)
1020 {
1021     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
1022 }
1023
1024 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
1025                  int srcX, int srcY, int width, int height, int destX, int destY)
1026 {
1027         cairo_t *cr;
1028         c_animBufs[anr+4] = CsBoardWindow(currBoard);
1029         cr = cairo_create (c_animBufs[anr+destBuf]);
1030         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
1031         cairo_rectangle (cr, destX, destY, width, height);
1032         cairo_fill (cr);
1033         cairo_destroy (cr);
1034         if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
1035             GraphExpose(currBoard, destX, destY, width, height);
1036 }
1037
1038 void
1039 SetDragPiece (AnimNr anr, ChessSquare piece)
1040 {
1041 }
1042
1043 /* [AS] Arrow highlighting support */
1044
1045 void
1046 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
1047 {
1048     cairo_t *cr;
1049     int i;
1050     cr = cairo_create (cs);
1051     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
1052     for (i=0;i<nr;i++) {
1053         cairo_line_to(cr, arrow[i].x, arrow[i].y);
1054     }
1055     if(appData.monoMode) { // should we always outline arrow?
1056         cairo_line_to(cr, arrow[0].x, arrow[0].y);
1057         SetPen(cr, 2, "#000000", 0);
1058         cairo_stroke_preserve(cr);
1059     }
1060     SetPen(cr, 2, appData.highlightSquareColor, 0);
1061     cairo_fill(cr);
1062
1063     /* free memory */
1064     cairo_destroy (cr);
1065 }
1066
1067 void
1068 DrawPolygon (Pnt arrow[], int nr)
1069 {
1070     DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1071 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1072 }
1073
1074 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1075
1076 static void
1077 ChoosePen(cairo_t *cr, int i)
1078 {
1079   switch(i) {
1080     case PEN_BLACK:
1081       SetPen(cr, 1.0, "#000000", 0);
1082       break;
1083     case PEN_DOTTED:
1084       SetPen(cr, 1.0, "#A0A0A0", 1);
1085       break;
1086     case PEN_BLUEDOTTED:
1087       SetPen(cr, 1.0, "#0000FF", 1);
1088       break;
1089     case PEN_BOLDWHITE:
1090       SetPen(cr, 3.0, crWhite, 0);
1091       break;
1092     case PEN_BOLDBLACK:
1093       SetPen(cr, 3.0, crBlack, 0);
1094       break;
1095     case PEN_BACKGD:
1096       SetPen(cr, 3.0, "#E0E0F0", 0);
1097       break;
1098   }
1099 }
1100
1101 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1102 void
1103 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1104 {
1105   static int curX, curY;
1106
1107   if(penType != PEN_NONE) {
1108     cairo_t *cr = cairo_create(CsBoardWindow(disp));
1109     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1110     cairo_move_to (cr, curX, curY);
1111     cairo_line_to (cr, x,y);
1112     ChoosePen(cr, penType);
1113     cairo_stroke (cr);
1114     cairo_destroy (cr);
1115   }
1116
1117   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1118   curX = x; curY = y;
1119 }
1120
1121 // front-end wrapper for drawing functions to do rectangles
1122 void
1123 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1124 {
1125   cairo_t *cr;
1126
1127   cr = cairo_create (CsBoardWindow(disp));
1128   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1129   cairo_rectangle (cr, left, top, right-left, bottom-top);
1130   switch(side)
1131     {
1132     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1133     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1134     case 2: ChoosePen(cr, PEN_BACKGD); break;
1135     }
1136   cairo_fill (cr);
1137
1138   if(style != FILLED)
1139     {
1140       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1141       ChoosePen(cr, PEN_BLACK);
1142       cairo_stroke (cr);
1143     }
1144
1145   cairo_destroy(cr);
1146 }
1147
1148 // front-end wrapper for putting text in graph
1149 void
1150 DrawEvalText (char *buf, int cbBuf, int y)
1151 {
1152     // the magic constants 8 and 5 should really be derived from the font size somehow
1153   cairo_text_extents_t extents;
1154   cairo_t *cr = cairo_create(CsBoardWindow(disp));
1155
1156   /* GTK-TODO this has to go into the font-selection */
1157   cairo_select_font_face (cr, "Sans",
1158                           CAIRO_FONT_SLANT_NORMAL,
1159                           CAIRO_FONT_WEIGHT_NORMAL);
1160   cairo_set_font_size (cr, 12.0);
1161
1162
1163   cairo_text_extents (cr, buf, &extents);
1164
1165   cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1166   cairo_text_path (cr, buf);
1167   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1168   cairo_fill_preserve (cr);
1169   cairo_set_source_rgb (cr, 0, 1.0, 0);
1170   cairo_set_line_width (cr, 0.1);
1171   cairo_stroke (cr);
1172
1173   /* free memory */
1174   cairo_destroy (cr);
1175 }