Provide help clicks on recently-used-engines menu items
[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         if(v == VariantChu) {
191            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteClaw];
192            pngPieceBitmaps[i][(int)WhiteClaw]    = pngPieceBitmaps2[i][(int)WhiteNightrider];
193            pngPieceBitmaps[i][(int)WhiteUnicorn] = pngPieceBitmaps2[i][(int)WhiteCat];
194            pngPieceBitmaps[i][(int)WhiteSilver]  = pngPieceBitmaps2[i][(int)WhiteSword];
195            pngPieceBitmaps[i][(int)WhiteFalcon]  = pngPieceBitmaps2[i][(int)WhiteDagger];
196            pngPieceBitmaps[i][(int)WhiteCat]     = pngPieceBitmaps2[i][(int)WhiteUnicorn];
197            pngPieceBitmaps[i][(int)WhiteSword]   = pngPieceBitmaps2[i][(int)WhiteSilver];
198            pngPieceBitmaps[i][(int)WhiteDagger]  = pngPieceBitmaps2[i][(int)WhiteFalcon];
199            pngPieceBitmaps[i][(int)WhiteMan]     = pngPieceBitmaps2[i][(int)WhiteCopper];
200            pngPieceBitmaps[i][(int)WhiteCopper]  = pngPieceBitmaps2[i][(int)WhiteMan];
201            pngPieceBitmaps[i][(int)WhiteAxe]     = pngPieceBitmaps2[i][(int)WhiteCannon];
202            pngPieceBitmaps[i][(int)WhiteCannon]  = pngPieceBitmaps2[i][(int)WhiteAxe];
203         }
204     }
205 }
206
207 #define BoardSize int
208 void
209 InitDrawingSizes (BoardSize boardSize, int flags)
210 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
211     int boardWidth, boardHeight;
212     static int oldWidth, oldHeight;
213     static VariantClass oldVariant;
214     static int oldTwoBoards = 0, oldNrOfFiles = 0;
215
216     if(!mainOptions[W_BOARD].handle) return;
217
218     if(boardSize == -2 && gameInfo.variant != oldVariant
219                        && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
220         squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
221         if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
222         squareSize -= lineGap;
223         CreatePNGPieces();
224         CreateGrid();
225     }
226     oldNrOfFiles = BOARD_WIDTH;
227
228     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
229     oldTwoBoards = twoBoards;
230
231     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
232     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
233     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
234
235   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
236
237     oldWidth = boardWidth; oldHeight = boardHeight;
238     CreateGrid();
239     CreateAnyPieces(0); // redo texture scaling
240
241     /*
242      * Inhibit shell resizing.
243      */
244     ResizeBoardWindow(boardWidth, boardHeight, 0);
245
246     DelayedDrag();
247   }
248
249     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
250     // (only for xpm)
251
252   if(gameInfo.variant != oldVariant) { // and only if variant changed
253
254     SelectPieces(gameInfo.variant);
255
256     oldVariant = gameInfo.variant;
257   }
258   CreateAnimVars();
259 }
260
261 void
262 ExposeRedraw (Option *graph, int x, int y, int w, int h)
263 {   // copy a selected part of the buffer bitmap to the display
264     cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
265     cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
266     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
267     cairo_rectangle(cr, x, y, w, h);
268     cairo_fill(cr);
269     cairo_destroy(cr);
270 }
271
272 static int modV[2], modH[2];
273
274 static void
275 CreatePNGBoard (char *s, int kind)
276 {
277     float w, h;
278     static float n[2] = { 1., 1. };
279     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
280     textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
281     if(strstr(s, ".png")) {
282         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
283         if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
284             char c, *p = s, *q;
285             int r, f;
286             if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
287             if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
288             useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
289             w = textureW[kind] = cairo_image_surface_get_width (img);
290             h = textureH[kind] = cairo_image_surface_get_height (img);
291             n[kind] = 1.; modV[kind] = modH[kind] = -1;
292             while((q = strchr(p+1, '-'))) p = q; // find last '-'
293             if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
294                 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
295                 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
296                 textureH[kind] = (h*BOARD_HEIGHT)/r;
297                 n[kind] = r*squareSize/h; // scale to make it fit exactly vertically
298                 modV[kind] = r; modH[kind] = f;
299             } else
300             if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
301                 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
302             } else {
303                 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
304             }
305             if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
306                 // create scaled-up copy of the raw png image when it was too small
307                 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
308                 cairo_t *cr = cairo_create(cs);
309                 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
310 //              cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
311                 cairo_scale(cr, n[kind], n[kind]);
312                 cairo_set_source_surface (cr, img, 0, 0);
313                 cairo_paint (cr);
314                 cairo_destroy (cr);
315             }
316         }
317     }
318 }
319
320 char *pngPieceNames[] = // must be in same order as internal piece encoding
321 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
322   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
323   "Sword", "Zebra", "Camel", "Tower", "Wolf", "Hat", "Duck", "Lance", "Dragon", "Gnu", "Cub",
324   "LShield", "Pegasus", "Wizard", "Copper", "Iron", "Viking", "Flag", "Axe", "Dolphin", "Leopard", "Claw",
325   "Left", "Butterfly", "PromoBishop", "PromoRook", "HCrown", "RShield", "Prince", "Phoenix", "Kylin", "Drunk", "Right",
326   "GoldPawn", "GoldKnight", "PromoHorse", "PromoDragon", "GoldLance", "GoldSilver", "HSword", "PromoSword", "PromoHSword", "Princess", "King",
327   NULL
328 };
329
330 char *backupPiece[] = { // pieces that map on other in default theme ("Crown" - "Drunk")
331   "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
332   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Chancellor", NULL,
333   NULL, "Knight", NULL, "Commoner", NULL, NULL, NULL, "Canon", NULL, NULL, NULL,
334   NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion", "Elephant"
335 };
336
337 RsvgHandle *
338 LoadSVG (char *dir, int color, int piece, int retry)
339 {
340     char buf[MSG_SIZ];
341   RsvgHandle *svg=svgPieces[color][piece];
342   RsvgDimensionData svg_dimensions;
343   GError *svgerror=NULL;
344   cairo_surface_t *img;
345   cairo_t *cr;
346   char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
347
348     if(!name) return NULL;
349
350     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
351
352     if(!svg && *dir) {
353       svg = rsvg_handle_new_from_file(buf, &svgerror);
354       if(!svg) { // failed! If -pid name starts with "sub_" we try to load the piece from the parent directory
355         char *p = buf, *q;
356         safeStrCpy(buf, dir, MSG_SIZ);
357         while((q = strchr(p, '/'))) p = q + 1;
358         if(!strncmp(p, "sub_", 4)) {
359           if(p == buf) safeStrCpy(buf, ".", MSG_SIZ); else p[-1] = NULLCHAR; // strip last directory off path
360           return LoadSVG(buf, color, piece, retry);
361         }
362       }
363       if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
364         snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
365         svg = rsvg_handle_new_from_file(buf, &svgerror);
366       }
367     }
368
369     if(svg) {
370       rsvg_handle_get_dimensions(svg, &svg_dimensions);
371       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
372
373       cr = cairo_create(img);
374       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
375       rsvg_handle_render_cairo(svg, cr);
376       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
377         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
378         pngPieceImages[color][piece] = img;
379       }
380       cairo_destroy(cr);
381
382       return svg;
383     }
384     if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
385         return LoadSVG(dir, color, piece, 1);
386     if(svgerror)
387         g_error_free(svgerror);
388     return NULL;
389 }
390
391 static void
392 ScaleOnePiece (int color, int piece)
393 {
394   float w, h;
395   char buf[MSG_SIZ];
396   cairo_surface_t *img, *cs;
397   cairo_t *cr;
398
399   g_type_init ();
400
401   svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
402
403   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
404     if(*appData.pieceDirectory) { // user specified piece directory
405       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
406       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
407       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
408         svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
409       } else pngPieceImages[color][piece] = img;
410     }
411   }
412
413   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
414     static int warned = 0;
415     if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg 
416        && !warned && strcmp(pngPieceNames[piece], "Tile")) {         // but do not complain about missing 'Tile'
417       char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
418       printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
419       DisplayError(msg, 0);
420       warned = 1; // prevent error message being repeated for each piece type
421     }
422   }
423
424   img = pngPieceImages[color][piece];
425
426   // create new bitmap to hold scaled piece image (and remove any old)
427   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
428   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
429
430   if(!img) return;
431
432   // scaled copying of the raw png image
433   cr = cairo_create(cs);
434   w = cairo_image_surface_get_width (img);
435   h = cairo_image_surface_get_height (img);
436   cairo_scale(cr, squareSize/w, squareSize/h);
437   cairo_set_source_surface (cr, img, 0, 0);
438   cairo_paint (cr);
439   cairo_destroy (cr);
440
441   if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
442     int stride = cairo_image_surface_get_stride(cs)/4;
443     int *buf = (int *) cairo_image_surface_get_data(cs);
444     int i, j, p;
445     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
446     cairo_surface_flush(cs);
447     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
448         int r, a;
449         float f;
450         unsigned int c = buf[i*stride + j];
451         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
452         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
453         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
454         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
455         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
456         if(appData.monoMode) {
457             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
458             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
459             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
460         }
461     }
462     cairo_surface_mark_dirty(cs);
463   }
464 }
465
466 void
467 CreatePNGPieces ()
468 {
469   int p;
470
471   for(p=0; pngPieceNames[p]; p++) {
472     ScaleOnePiece(0, p);
473     ScaleOnePiece(1, p);
474   }
475   SelectPieces(gameInfo.variant);
476 }
477
478 void
479 CreateAnyPieces (int p)
480 {   // [HGM] taken out of main
481     if(p) CreatePNGPieces();
482     CreatePNGBoard(appData.liteBackTextureFile, 1);
483     CreatePNGBoard(appData.darkBackTextureFile, 0);
484 }
485
486 void
487 InitDrawingParams (int reloadPieces)
488 {
489     int i, p;
490     if(reloadPieces)
491     for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
492         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
493         pngPieceImages[i][p] = NULL;
494         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
495         svgPieces[i][p] = NULL;
496     }
497     CreateAnyPieces(1);
498 }
499
500 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
501
502 float
503 Color (char *col, int n)
504 {
505   int c;
506   sscanf(col, "#%x", &c);
507   c = c >> 4*n & 255;
508   return c/255.;
509 }
510
511 void
512 SetPen (cairo_t *cr, float w, char *col, int dash)
513 {
514   static const double dotted[] = {4.0, 4.0};
515   static int len  = sizeof(dotted) / sizeof(dotted[0]);
516   cairo_set_line_width (cr, w);
517   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
518   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
519 }
520
521 void DrawSeekAxis( int x, int y, int xTo, int yTo )
522 {
523     cairo_t *cr;
524
525     /* get a cairo_t */
526     cr = cairo_create (CsBoardWindow(currBoard));
527
528     cairo_move_to (cr, x, y);
529     cairo_line_to(cr, xTo, yTo );
530
531     SetPen(cr, 2, "#000000", 0);
532     cairo_stroke(cr);
533
534     /* free memory */
535     cairo_destroy (cr);
536     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
537 }
538
539 void DrawSeekBackground( int left, int top, int right, int bottom )
540 {
541     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
542
543     cairo_rectangle (cr, left, top, right-left, bottom-top);
544
545     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
546     cairo_fill(cr);
547
548     /* free memory */
549     cairo_destroy (cr);
550     GraphExpose(currBoard, left, top, right-left, bottom-top);
551 }
552
553 void DrawSeekText(char *buf, int x, int y)
554 {
555     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
556
557     cairo_select_font_face (cr, "Sans",
558                             CAIRO_FONT_SLANT_NORMAL,
559                             CAIRO_FONT_WEIGHT_NORMAL);
560
561     cairo_set_font_size (cr, 12.0);
562
563     cairo_move_to (cr, x, y+4);
564     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
565     cairo_show_text( cr, buf);
566
567     /* free memory */
568     cairo_destroy (cr);
569     GraphExpose(currBoard, x-5, y-10, 60, 15);
570 }
571
572 void DrawSeekDot(int x, int y, int colorNr)
573 {
574     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
575     int square = colorNr & 0x80;
576     colorNr &= 0x7F;
577
578     if(square)
579         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
580     else
581         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
582
583     SetPen(cr, 2, "#000000", 0);
584     cairo_stroke_preserve(cr);
585     switch (colorNr) {
586       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
587       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
588       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
589     }
590     cairo_fill(cr);
591
592     /* free memory */
593     cairo_destroy (cr);
594     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
595 }
596
597 void
598 InitDrawingHandle (Option *opt)
599 {
600 //    CsBoardWindow = DRAWABLE(opt);
601     currBoard = opt;
602 }
603
604 void
605 CreateGrid ()
606 {
607     int i, j;
608
609     if (lineGap == 0) return;
610
611     /* [HR] Split this into 2 loops for non-square boards. */
612
613     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
614         gridSegments[i].x1 = 0;
615         gridSegments[i].x2 =
616           lineGap + BOARD_WIDTH * (squareSize + lineGap);
617         gridSegments[i].y1 = gridSegments[i].y2
618           = lineGap / 2 + (i * (squareSize + lineGap));
619     }
620
621     for (j = 0; j < BOARD_WIDTH + 1; j++) {
622         gridSegments[j + i].y1 = 0;
623         gridSegments[j + i].y2 =
624           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
625         gridSegments[j + i].x1 = gridSegments[j + i].x2
626           = lineGap / 2 + (j * (squareSize + lineGap));
627     }
628 }
629
630 void
631 DrawGrid()
632 {
633   /* draws a grid starting around Nx, Ny squares starting at x,y */
634   int i;
635   float odd = (lineGap & 1)/2.;
636   cairo_t *cr;
637
638   /* get a cairo_t */
639   cr = cairo_create (CsBoardWindow(currBoard));
640
641   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
642   SetPen(cr, lineGap, "#000000", 0);
643
644   /* lines in X */
645   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
646     {
647       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
648       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
649       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
650       cairo_stroke (cr);
651     }
652
653   /* free memory */
654   cairo_destroy (cr);
655
656   return;
657 }
658
659 void
660 DrawBorder (int x, int y, int type, int odd)
661 {
662     cairo_t *cr;
663     char *col;
664
665     switch(type) {
666         case 0: col = "#000000"; break;
667         case 1: col = appData.highlightSquareColor; break;
668         case 2: col = appData.premoveHighlightColor; break;
669         default: col = "#808080"; break; // cannot happen
670     }
671     cr = cairo_create(CsBoardWindow(currBoard));
672     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
673     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
674     SetPen(cr, lineGap, col, 0);
675     cairo_stroke(cr);
676     cairo_destroy(cr);
677 //    GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
678 }
679
680 static int
681 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
682 {
683     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
684     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
685     *x0 = 0; *y0 = 0;
686     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
687     if(modV[kind] > 0) nx %= modH[kind], ny %= modV[kind]; // tile fixed-format board periodically to extend it
688     if(textureW[kind] < W*squareSize)
689         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
690     else
691         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
692     if(textureH[kind] < H*squareSize)
693         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
694     else
695         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
696     return 1;
697 }
698
699 void
700 DrawLogo (Option *opt, void *logo)
701 {
702     cairo_surface_t *img;
703     cairo_t *cr;
704     int w, h;
705
706     if(!opt) return;
707     cr = cairo_create(CsBoardWindow(opt));
708     cairo_rectangle (cr, 0, 0, opt->max, opt->value);
709     cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
710     cairo_fill(cr); // paint background in case logo does not exist
711     if(logo) {
712         img = cairo_image_surface_create_from_png (logo);
713         if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
714             w = cairo_image_surface_get_width (img);
715             h = cairo_image_surface_get_height (img);
716 //        cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
717             cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
718             cairo_set_source_surface (cr, img, 0, 0);
719             cairo_paint (cr);
720         }
721         cairo_surface_destroy (img);
722     }
723     cairo_destroy (cr);
724     GraphExpose(opt, 0, 0, opt->max, opt->value);
725 }
726
727 static void
728 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
729 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
730     int x0, y0;
731     cairo_t *cr;
732
733     cr = cairo_create (dest);
734
735     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
736             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
737             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
738             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
739             cairo_fill (cr);
740             cairo_destroy (cr);
741     } else { // evenly colored squares
742         char *col = NULL;
743         switch (color) {
744           case 0: col = appData.darkSquareColor; break;
745           case 1: col = appData.lightSquareColor; break;
746           case 2: col = "#000000"; break;
747           default: col = "#808080"; break; // cannot happen
748         }
749         SetPen(cr, 2.0, col, 0);
750         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
751         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
752         cairo_fill (cr);
753         cairo_destroy (cr);
754     }
755 }
756
757 static void
758 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
759 {
760     int kind;
761     cairo_t *cr;
762
763     if ((int)piece < (int) BlackPawn) {
764         kind = 0;
765     } else {
766         kind = 1;
767         piece -= BlackPawn;
768     }
769     if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
770     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
771     cr = cairo_create (dest);
772     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
773     cairo_paint(cr);
774     cairo_destroy (cr);
775 }
776
777 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
778
779 void
780 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
781 {
782         cairo_t *cr;
783
784         cr = cairo_create(cs);
785         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
786         if(appData.monoMode) {
787             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
788             cairo_stroke_preserve(cr);
789             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
790         } else {
791             SetPen(cr, 2, markerColor[marker-1], 0);
792         }
793         cairo_fill(cr);
794
795         cairo_destroy(cr);
796 }
797
798 void
799 DrawDot (int marker, int x, int y, int r)
800 { // used for atomic captures; no need to draw on backup
801   DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
802   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
803 }
804
805 static void
806 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
807 {
808 //      cairo_text_extents_t te;
809         cairo_t *cr;
810         int s = 1 - 2*flip;
811         PangoLayout *layout;
812         PangoFontDescription *desc;
813         PangoRectangle r;
814         char fontName[MSG_SIZ];
815
816         cr = cairo_create (canvas);
817         layout = pango_cairo_create_layout(cr);
818         pango_layout_set_text(layout, string, -1);
819         snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
820         desc = pango_font_description_from_string(fontName);
821         pango_layout_set_font_description(layout, desc);
822         pango_font_description_free(desc);
823         pango_layout_get_pixel_extents(layout, NULL, &r);
824         cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
825         if(s < 0) cairo_rotate(cr, G_PI);
826         cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
827         pango_cairo_update_layout(cr, layout);
828         pango_cairo_show_layout(cr, layout);
829         g_object_unref(layout);
830         cairo_destroy(cr);
831 }
832
833 void
834 DrawText (char *string, int x, int y, int align)
835 {
836         int xx = x, yy = y;
837         cairo_text_extents_t te;
838         cairo_t *cr;
839
840         cr = cairo_create (CsBoardWindow(currBoard));
841         cairo_select_font_face (cr, "Sans",
842                     CAIRO_FONT_SLANT_NORMAL,
843                     CAIRO_FONT_WEIGHT_BOLD);
844
845         cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
846         // calculate where it goes
847         cairo_text_extents (cr, string, &te);
848
849         if (align == 1) {
850             xx += squareSize - te.width - te.x_bearing - 1;
851             yy += squareSize - te.height - te.y_bearing - 1;
852         } else if (align == 2) {
853             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
854         } else if (align == 3) {
855             xx += squareSize - te.width -te.x_bearing - 1;
856             yy += -te.y_bearing + 3;
857         } else if (align == 4) {
858             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
859         }
860
861         cairo_move_to (cr, xx-1, yy);
862         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
863         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
864         cairo_show_text (cr, string);
865         cairo_destroy (cr);
866 }
867
868 void
869 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
870 {
871     char *p, *q, buf[10];
872     int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
873     if(piece == EmptySquare) return;
874     if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
875     p = appData.inscriptions;
876     n = piece;
877     while(piece > WhitePawn) {
878       if(*p++ == NULLCHAR) {
879         if(n != WhiteKing) return;
880         p = q;
881         break;
882       }
883       q = p - 1;
884       while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
885       piece--;
886     }
887     strncpy(buf, p, 10);
888     for(q=buf; (*++q & 0xC0) == 0x80;);
889     *q = NULLCHAR;
890     DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
891 }
892
893 void
894 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
895 {   // basic front-end board-draw function: takes care of everything that can be in square:
896     // piece, background, coordinate/count, marker dot
897
898     if (piece == EmptySquare) {
899         BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
900     } else {
901         pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
902         if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
903     }
904
905     if(align) { // square carries inscription (coord or piece count)
906         if(align > 1) DrawText(tString, x, y, align);       // top (rank or count)
907         if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
908     }
909
910     if(marker) { // print fat marker dot, if requested
911         DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
912     }
913 }
914
915 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
916
917 /*      Masks for XPM pieces. Black and white pieces can have
918         different shapes, but in the interest of retaining my
919         sanity pieces must have the same outline on both light
920         and dark squares, and all pieces must use the same
921         background square colors/images.                */
922
923 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
924
925 static void
926 InitAnimState (AnimNr anr)
927 {
928     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
929     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
930     c_animBufs[anr+4] = CsBoardWindow(currBoard);
931     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
932     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
933 }
934
935 void
936 CreateAnimVars ()
937 {
938   InitAnimState(Game);
939   InitAnimState(Player);
940 }
941
942 static void
943 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
944 {
945   static cairo_t *pieceSource;
946   pieceSource = cairo_create (dest);
947   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
948   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
949   else cairo_paint(pieceSource);
950   cairo_destroy (pieceSource);
951   if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
952 }
953
954 void
955 InsertPiece (AnimNr anr, ChessSquare piece)
956 {
957     CairoOverlayPiece(piece, c_animBufs[anr]);
958 }
959
960 void
961 DrawBlank (AnimNr anr, int x, int y, int startColor)
962 {
963     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
964 }
965
966 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
967                  int srcX, int srcY, int width, int height, int destX, int destY)
968 {
969         cairo_t *cr;
970         c_animBufs[anr+4] = CsBoardWindow(currBoard);
971         cr = cairo_create (c_animBufs[anr+destBuf]);
972         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
973         cairo_rectangle (cr, destX, destY, width, height);
974         cairo_fill (cr);
975         cairo_destroy (cr);
976         if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
977             GraphExpose(currBoard, destX, destY, width, height);
978 }
979
980 void
981 SetDragPiece (AnimNr anr, ChessSquare piece)
982 {
983 }
984
985 /* [AS] Arrow highlighting support */
986
987 void
988 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
989 {
990     cairo_t *cr;
991     int i;
992     cr = cairo_create (cs);
993     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
994     for (i=0;i<nr;i++) {
995         cairo_line_to(cr, arrow[i].x, arrow[i].y);
996     }
997     if(appData.monoMode) { // should we always outline arrow?
998         cairo_line_to(cr, arrow[0].x, arrow[0].y);
999         SetPen(cr, 2, "#000000", 0);
1000         cairo_stroke_preserve(cr);
1001     }
1002     SetPen(cr, 2, appData.highlightSquareColor, 0);
1003     cairo_fill(cr);
1004
1005     /* free memory */
1006     cairo_destroy (cr);
1007 }
1008
1009 void
1010 DrawPolygon (Pnt arrow[], int nr)
1011 {
1012     DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1013 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1014 }
1015
1016 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1017
1018 static void
1019 ChoosePen(cairo_t *cr, int i)
1020 {
1021   switch(i) {
1022     case PEN_BLACK:
1023       SetPen(cr, 1.0, "#000000", 0);
1024       break;
1025     case PEN_DOTTED:
1026       SetPen(cr, 1.0, "#A0A0A0", 1);
1027       break;
1028     case PEN_BLUEDOTTED:
1029       SetPen(cr, 1.0, "#0000FF", 1);
1030       break;
1031     case PEN_BOLDWHITE:
1032       SetPen(cr, 3.0, crWhite, 0);
1033       break;
1034     case PEN_BOLDBLACK:
1035       SetPen(cr, 3.0, crBlack, 0);
1036       break;
1037     case PEN_BACKGD:
1038       SetPen(cr, 3.0, "#E0E0F0", 0);
1039       break;
1040   }
1041 }
1042
1043 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1044 void
1045 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1046 {
1047   static int curX, curY;
1048
1049   if(penType != PEN_NONE) {
1050     cairo_t *cr = cairo_create(CsBoardWindow(disp));
1051     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1052     cairo_move_to (cr, curX, curY);
1053     cairo_line_to (cr, x,y);
1054     ChoosePen(cr, penType);
1055     cairo_stroke (cr);
1056     cairo_destroy (cr);
1057   }
1058
1059   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1060   curX = x; curY = y;
1061 }
1062
1063 // front-end wrapper for drawing functions to do rectangles
1064 void
1065 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1066 {
1067   cairo_t *cr;
1068
1069   cr = cairo_create (CsBoardWindow(disp));
1070   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1071   cairo_rectangle (cr, left, top, right-left, bottom-top);
1072   switch(side)
1073     {
1074     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1075     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1076     case 2: ChoosePen(cr, PEN_BACKGD); break;
1077     }
1078   cairo_fill (cr);
1079
1080   if(style != FILLED)
1081     {
1082       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1083       ChoosePen(cr, PEN_BLACK);
1084       cairo_stroke (cr);
1085     }
1086
1087   cairo_destroy(cr);
1088 }
1089
1090 // front-end wrapper for putting text in graph
1091 void
1092 DrawEvalText (char *buf, int cbBuf, int y)
1093 {
1094     // the magic constants 8 and 5 should really be derived from the font size somehow
1095   cairo_text_extents_t extents;
1096   cairo_t *cr = cairo_create(CsBoardWindow(disp));
1097
1098   /* GTK-TODO this has to go into the font-selection */
1099   cairo_select_font_face (cr, "Sans",
1100                           CAIRO_FONT_SLANT_NORMAL,
1101                           CAIRO_FONT_WEIGHT_NORMAL);
1102   cairo_set_font_size (cr, 12.0);
1103
1104
1105   cairo_text_extents (cr, buf, &extents);
1106
1107   cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1108   cairo_text_path (cr, buf);
1109   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1110   cairo_fill_preserve (cr);
1111   cairo_set_source_rgb (cr, 0, 1.0, 0);
1112   cairo_set_line_width (cr, 0.1);
1113   cairo_stroke (cr);
1114
1115   /* free memory */
1116   cairo_destroy (cr);
1117 }