Implement ChuChess
[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)WhiteKing]; // 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", "King", "Queen", "Lion", "PromoRook", "PromoHSword",
243   "Dolphin", "Chancellor", "Unicorn", "Hawk", "Sword", "Princess", "HCrown", "Knight", "Elephant", "PromoBishop", "King",
244   "Claw", "GoldKnight", "GoldLance", "GoldSilver", NULL
245 };
246
247 RsvgHandle *
248 LoadSVG (char *dir, int color, int piece)
249 {
250     char buf[MSG_SIZ];
251   RsvgHandle *svg=svgPieces[color][piece];
252   RsvgDimensionData svg_dimensions;
253   GError *svgerror=NULL;
254   cairo_surface_t *img;
255   cairo_t *cr;
256
257     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", pngPieceNames[piece]);
258
259     if(svg || *dir && (svg = rsvg_handle_new_from_file(buf, &svgerror))) {
260
261       rsvg_handle_get_dimensions(svg, &svg_dimensions);
262       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
263
264       cr = cairo_create(img);
265       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
266       rsvg_handle_render_cairo(svg, cr);
267       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
268         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
269         pngPieceImages[color][piece] = img;
270       }
271       cairo_destroy(cr);
272
273       return svg;
274     }
275     if(svgerror)
276         g_error_free(svgerror);
277     return NULL;
278 }
279
280 static void
281 ScaleOnePiece (int color, int piece)
282 {
283   float w, h;
284   char buf[MSG_SIZ];
285   cairo_surface_t *img, *cs;
286   cairo_t *cr;
287
288   g_type_init ();
289
290   svgPieces[color][piece] = LoadSVG("", color, piece); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
291
292   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
293     if(*appData.pieceDirectory) { // user specified piece directory
294       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
295       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
296       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
297         svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece); // so try if he has svg there
298       } else pngPieceImages[color][piece] = img;
299     }
300   }
301
302   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
303     static int warned = 0;
304     if(!(svgPieces[color][piece] = LoadSVG(SVGDIR, color, piece)) && !warned) { // try to fall back on installed svg
305       char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
306       printf("%s\n", msg); // give up
307       DisplayError(msg, 0);
308       warned = 1; // prevent error message being repeated for each piece type
309     }
310   }
311
312   img = pngPieceImages[color][piece];
313
314   // create new bitmap to hold scaled piece image (and remove any old)
315   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
316   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
317
318   if(!img) return;
319
320   // scaled copying of the raw png image
321   cr = cairo_create(cs);
322   w = cairo_image_surface_get_width (img);
323   h = cairo_image_surface_get_height (img);
324   cairo_scale(cr, squareSize/w, squareSize/h);
325   cairo_set_source_surface (cr, img, 0, 0);
326   cairo_paint (cr);
327   cairo_destroy (cr);
328
329   if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
330     int stride = cairo_image_surface_get_stride(cs)/4;
331     int *buf = (int *) cairo_image_surface_get_data(cs);
332     int i, j, p;
333     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
334     cairo_surface_flush(cs);
335     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
336         int r, a;
337         float f;
338         unsigned int c = buf[i*stride + j];
339         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
340         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
341         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
342         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
343         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
344         if(appData.monoMode) {
345             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
346             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
347             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
348         }
349     }
350     cairo_surface_mark_dirty(cs);
351   }
352 }
353
354 void
355 CreatePNGPieces ()
356 {
357   int p;
358
359   for(p=0; pngPieceNames[p]; p++) {
360     ScaleOnePiece(0, p);
361     ScaleOnePiece(1, p);
362   }
363   SelectPieces(gameInfo.variant);
364 }
365
366 void
367 CreateAnyPieces ()
368 {   // [HGM] taken out of main
369     CreatePNGPieces();
370     CreatePNGBoard(appData.liteBackTextureFile, 1);
371     CreatePNGBoard(appData.darkBackTextureFile, 0);
372 }
373
374 void
375 InitDrawingParams (int reloadPieces)
376 {
377     int i, p;
378     if(reloadPieces)
379     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
380         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
381         pngPieceImages[i][p] = NULL;
382         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
383         svgPieces[i][p] = NULL;
384     }
385     CreateAnyPieces();
386 }
387
388 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
389
390 float
391 Color (char *col, int n)
392 {
393   int c;
394   sscanf(col, "#%x", &c);
395   c = c >> 4*n & 255;
396   return c/255.;
397 }
398
399 void
400 SetPen (cairo_t *cr, float w, char *col, int dash)
401 {
402   static const double dotted[] = {4.0, 4.0};
403   static int len  = sizeof(dotted) / sizeof(dotted[0]);
404   cairo_set_line_width (cr, w);
405   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
406   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
407 }
408
409 void DrawSeekAxis( int x, int y, int xTo, int yTo )
410 {
411     cairo_t *cr;
412
413     /* get a cairo_t */
414     cr = cairo_create (csBoardWindow);
415
416     cairo_move_to (cr, x, y);
417     cairo_line_to(cr, xTo, yTo );
418
419     SetPen(cr, 2, "#000000", 0);
420     cairo_stroke(cr);
421
422     /* free memory */
423     cairo_destroy (cr);
424     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
425 }
426
427 void DrawSeekBackground( int left, int top, int right, int bottom )
428 {
429     cairo_t *cr = cairo_create (csBoardWindow);
430
431     cairo_rectangle (cr, left, top, right-left, bottom-top);
432
433     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
434     cairo_fill(cr);
435
436     /* free memory */
437     cairo_destroy (cr);
438     GraphExpose(currBoard, left, top, right-left, bottom-top);
439 }
440
441 void DrawSeekText(char *buf, int x, int y)
442 {
443     cairo_t *cr = cairo_create (csBoardWindow);
444
445     cairo_select_font_face (cr, "Sans",
446                             CAIRO_FONT_SLANT_NORMAL,
447                             CAIRO_FONT_WEIGHT_NORMAL);
448
449     cairo_set_font_size (cr, 12.0);
450
451     cairo_move_to (cr, x, y+4);
452     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
453     cairo_show_text( cr, buf);
454
455     /* free memory */
456     cairo_destroy (cr);
457     GraphExpose(currBoard, x-5, y-10, 60, 15);
458 }
459
460 void DrawSeekDot(int x, int y, int colorNr)
461 {
462     cairo_t *cr = cairo_create (csBoardWindow);
463     int square = colorNr & 0x80;
464     colorNr &= 0x7F;
465
466     if(square)
467         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
468     else
469         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
470
471     SetPen(cr, 2, "#000000", 0);
472     cairo_stroke_preserve(cr);
473     switch (colorNr) {
474       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
475       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
476       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
477     }
478     cairo_fill(cr);
479
480     /* free memory */
481     cairo_destroy (cr);
482     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
483 }
484
485 void
486 InitDrawingHandle (Option *opt)
487 {
488     csBoardWindow = DRAWABLE(opt);
489 }
490
491 void
492 CreateGrid ()
493 {
494     int i, j;
495
496     if (lineGap == 0) return;
497
498     /* [HR] Split this into 2 loops for non-square boards. */
499
500     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
501         gridSegments[i].x1 = 0;
502         gridSegments[i].x2 =
503           lineGap + BOARD_WIDTH * (squareSize + lineGap);
504         gridSegments[i].y1 = gridSegments[i].y2
505           = lineGap / 2 + (i * (squareSize + lineGap));
506     }
507
508     for (j = 0; j < BOARD_WIDTH + 1; j++) {
509         gridSegments[j + i].y1 = 0;
510         gridSegments[j + i].y2 =
511           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
512         gridSegments[j + i].x1 = gridSegments[j + i].x2
513           = lineGap / 2 + (j * (squareSize + lineGap));
514     }
515 }
516
517 void
518 DrawGrid()
519 {
520   /* draws a grid starting around Nx, Ny squares starting at x,y */
521   int i;
522   float odd = (lineGap & 1)/2.;
523   cairo_t *cr;
524
525   /* get a cairo_t */
526   cr = cairo_create (csBoardWindow);
527
528   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
529   SetPen(cr, lineGap, "#000000", 0);
530
531   /* lines in X */
532   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
533     {
534       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
535       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
536       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
537       cairo_stroke (cr);
538     }
539
540   /* free memory */
541   cairo_destroy (cr);
542
543   return;
544 }
545
546 void
547 DrawBorder (int x, int y, int type, int odd)
548 {
549     cairo_t *cr;
550     char *col;
551
552     switch(type) {
553         case 0: col = "#000000"; break;
554         case 1: col = appData.highlightSquareColor; break;
555         case 2: col = appData.premoveHighlightColor; break;
556         default: col = "#808080"; break; // cannot happen
557     }
558     cr = cairo_create(csBoardWindow);
559     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
560     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
561     SetPen(cr, lineGap, col, 0);
562     cairo_stroke(cr);
563     cairo_destroy(cr);
564     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
565 }
566
567 static int
568 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
569 {
570     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
571     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
572     *x0 = 0; *y0 = 0;
573     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
574     if(textureW[kind] < W*squareSize)
575         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
576     else
577         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
578     if(textureH[kind] < H*squareSize)
579         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
580     else
581         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
582     return 1;
583 }
584
585 void
586 DrawLogo (Option *opt, void *logo)
587 {
588     cairo_surface_t *img;
589     cairo_t *cr;
590     int w, h;
591
592     if(!logo || !opt) return;
593     img = cairo_image_surface_create_from_png (logo);
594     w = cairo_image_surface_get_width (img);
595     h = cairo_image_surface_get_height (img);
596     cr = cairo_create(DRAWABLE(opt));
597 //    cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
598     cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
599     cairo_set_source_surface (cr, img, 0, 0);
600     cairo_paint (cr);
601     cairo_destroy (cr);
602     cairo_surface_destroy (img);
603     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
604 }
605
606 static void
607 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
608 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
609     int x0, y0;
610     cairo_t *cr;
611
612     cr = cairo_create (dest);
613
614     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
615             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
616             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
617             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
618             cairo_fill (cr);
619             cairo_destroy (cr);
620     } else { // evenly colored squares
621         char *col = NULL;
622         switch (color) {
623           case 0: col = appData.darkSquareColor; break;
624           case 1: col = appData.lightSquareColor; break;
625           case 2: col = "#000000"; break;
626           default: col = "#808080"; break; // cannot happen
627         }
628         SetPen(cr, 2.0, col, 0);
629         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
630         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
631         cairo_fill (cr);
632         cairo_destroy (cr);
633     }
634 }
635
636 static void
637 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
638 {
639     int kind;
640     cairo_t *cr;
641
642     if ((int)piece < (int) BlackPawn) {
643         kind = 0;
644     } else {
645         kind = 1;
646         piece -= BlackPawn;
647     }
648     if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
649     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
650     cr = cairo_create (dest);
651     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
652     cairo_paint(cr);
653     cairo_destroy (cr);
654 }
655
656 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
657
658 void
659 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
660 {
661         cairo_t *cr;
662
663         cr = cairo_create(cs);
664         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
665         if(appData.monoMode) {
666             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
667             cairo_stroke_preserve(cr);
668             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
669         } else {
670             SetPen(cr, 2, markerColor[marker-1], 0);
671         }
672         cairo_fill(cr);
673
674         cairo_destroy(cr);
675 }
676
677 void
678 DrawDot (int marker, int x, int y, int r)
679 { // used for atomic captures; no need to draw on backup
680   DoDrawDot(csBoardWindow, marker, x, y, r);
681   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
682 }
683
684 static void
685 DrawText (char *string, int x, int y, int align)
686 {
687         int xx = x, yy = y;
688         cairo_text_extents_t te;
689         cairo_t *cr;
690
691         cr = cairo_create (csBoardWindow);
692         cairo_select_font_face (cr, "Sans",
693                     CAIRO_FONT_SLANT_NORMAL,
694                     CAIRO_FONT_WEIGHT_BOLD);
695
696         cairo_set_font_size (cr, squareSize/4);
697         // calculate where it goes
698         cairo_text_extents (cr, string, &te);
699
700         if (align == 1) {
701             xx += squareSize - te.width - te.x_bearing - 1;
702             yy += squareSize - te.height - te.y_bearing - 1;
703         } else if (align == 2) {
704             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
705         } else if (align == 3) {
706             xx += squareSize - te.width -te.x_bearing - 1;
707             yy += -te.y_bearing + 3;
708         } else if (align == 4) {
709             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
710         }
711
712         cairo_move_to (cr, xx-1, yy);
713         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
714         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
715         cairo_show_text (cr, string);
716         cairo_destroy (cr);
717 }
718
719 void
720 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
721 {   // basic front-end board-draw function: takes care of everything that can be in square:
722     // piece, background, coordinate/count, marker dot
723
724     if (piece == EmptySquare) {
725         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
726     } else {
727         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
728     }
729
730     if(align) { // square carries inscription (coord or piece count)
731         if(align > 1) DrawText(tString, x, y, align);       // top (rank or count)
732         if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
733     }
734
735     if(marker) { // print fat marker dot, if requested
736         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
737     }
738 }
739
740 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
741
742 /*      Masks for XPM pieces. Black and white pieces can have
743         different shapes, but in the interest of retaining my
744         sanity pieces must have the same outline on both light
745         and dark squares, and all pieces must use the same
746         background square colors/images.                */
747
748 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
749
750 static void
751 InitAnimState (AnimNr anr)
752 {
753     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
754     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
755     c_animBufs[anr+4] = csBoardWindow;
756     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
757     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
758 }
759
760 void
761 CreateAnimVars ()
762 {
763   InitAnimState(Game);
764   InitAnimState(Player);
765 }
766
767 static void
768 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
769 {
770   static cairo_t *pieceSource;
771   pieceSource = cairo_create (dest);
772   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
773   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
774   else cairo_paint(pieceSource);
775   cairo_destroy (pieceSource);
776 }
777
778 void
779 InsertPiece (AnimNr anr, ChessSquare piece)
780 {
781     CairoOverlayPiece(piece, c_animBufs[anr]);
782 }
783
784 void
785 DrawBlank (AnimNr anr, int x, int y, int startColor)
786 {
787     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
788 }
789
790 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
791                  int srcX, int srcY, int width, int height, int destX, int destY)
792 {
793         cairo_t *cr;
794         c_animBufs[anr+4] = csBoardWindow;
795         cr = cairo_create (c_animBufs[anr+destBuf]);
796         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
797         cairo_rectangle (cr, destX, destY, width, height);
798         cairo_fill (cr);
799         cairo_destroy (cr);
800         if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
801             GraphExpose(currBoard, destX, destY, width, height);
802 }
803
804 void
805 SetDragPiece (AnimNr anr, ChessSquare piece)
806 {
807 }
808
809 /* [AS] Arrow highlighting support */
810
811 void
812 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
813 {
814     cairo_t *cr;
815     int i;
816     cr = cairo_create (cs);
817     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
818     for (i=0;i<nr;i++) {
819         cairo_line_to(cr, arrow[i].x, arrow[i].y);
820     }
821     if(appData.monoMode) { // should we always outline arrow?
822         cairo_line_to(cr, arrow[0].x, arrow[0].y);
823         SetPen(cr, 2, "#000000", 0);
824         cairo_stroke_preserve(cr);
825     }
826     SetPen(cr, 2, appData.highlightSquareColor, 0);
827     cairo_fill(cr);
828
829     /* free memory */
830     cairo_destroy (cr);
831 }
832
833 void
834 DrawPolygon (Pnt arrow[], int nr)
835 {
836     DoDrawPolygon(csBoardWindow, arrow, nr);
837 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
838 }
839
840 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
841
842 static void
843 ChoosePen(cairo_t *cr, int i)
844 {
845   switch(i) {
846     case PEN_BLACK:
847       SetPen(cr, 1.0, "#000000", 0);
848       break;
849     case PEN_DOTTED:
850       SetPen(cr, 1.0, "#A0A0A0", 1);
851       break;
852     case PEN_BLUEDOTTED:
853       SetPen(cr, 1.0, "#0000FF", 1);
854       break;
855     case PEN_BOLDWHITE:
856       SetPen(cr, 3.0, crWhite, 0);
857       break;
858     case PEN_BOLDBLACK:
859       SetPen(cr, 3.0, crBlack, 0);
860       break;
861     case PEN_BACKGD:
862       SetPen(cr, 3.0, "#E0E0F0", 0);
863       break;
864   }
865 }
866
867 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
868 void
869 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
870 {
871   static int curX, curY;
872
873   if(penType != PEN_NONE) {
874     cairo_t *cr = cairo_create(DRAWABLE(disp));
875     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
876     cairo_move_to (cr, curX, curY);
877     cairo_line_to (cr, x,y);
878     ChoosePen(cr, penType);
879     cairo_stroke (cr);
880     cairo_destroy (cr);
881   }
882
883   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
884   curX = x; curY = y;
885 }
886
887 // front-end wrapper for drawing functions to do rectangles
888 void
889 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
890 {
891   cairo_t *cr;
892
893   cr = cairo_create (DRAWABLE(disp));
894   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
895   cairo_rectangle (cr, left, top, right-left, bottom-top);
896   switch(side)
897     {
898     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
899     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
900     case 2: ChoosePen(cr, PEN_BACKGD); break;
901     }
902   cairo_fill (cr);
903
904   if(style != FILLED)
905     {
906       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
907       ChoosePen(cr, PEN_BLACK);
908       cairo_stroke (cr);
909     }
910
911   cairo_destroy(cr);
912 }
913
914 // front-end wrapper for putting text in graph
915 void
916 DrawEvalText (char *buf, int cbBuf, int y)
917 {
918     // the magic constants 8 and 5 should really be derived from the font size somehow
919   cairo_text_extents_t extents;
920   cairo_t *cr = cairo_create(DRAWABLE(disp));
921
922   /* GTK-TODO this has to go into the font-selection */
923   cairo_select_font_face (cr, "Sans",
924                           CAIRO_FONT_SLANT_NORMAL,
925                           CAIRO_FONT_WEIGHT_NORMAL);
926   cairo_set_font_size (cr, 12.0);
927
928
929   cairo_text_extents (cr, buf, &extents);
930
931   cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
932   cairo_text_path (cr, buf);
933   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
934   cairo_fill_preserve (cr);
935   cairo_set_source_rgb (cr, 0, 1.0, 0);
936   cairo_set_line_width (cr, 0.1);
937   cairo_stroke (cr);
938
939   /* free memory */
940   cairo_destroy (cr);
941 }