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