Update sr.po translation
[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
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
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
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
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
485 InitDrawingParams (int reloadPieces)
486 {
487     if(reloadPieces) ClearPieces();
488     CreateAnyPieces(1);
489 }
490
491 void
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
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
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
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
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
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
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
612 InitDrawingHandle (Option *opt)
613 {
614 //    CsBoardWindow = DRAWABLE(opt);
615     currBoard = opt;
616 }
617
618 void
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
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
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
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
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
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
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
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
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
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
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
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
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
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
961 CreateAnimVars ()
962 {
963   InitAnimState(Game);
964   InitAnimState(Player);
965 }
966
967 static void
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
980 InsertPiece (AnimNr anr, ChessSquare piece)
981 {
982     CairoOverlayPiece(piece, c_animBufs[anr]);
983 }
984
985 void
986 DrawBlank (AnimNr anr, int x, int y, int startColor)
987 {
988     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
989 }
990
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
1006 SetDragPiece (AnimNr anr, ChessSquare piece)
1007 {
1008 }
1009
1010 /* [AS] Arrow highlighting support */
1011
1012 void
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
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
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
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
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
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 }