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