Fix exposure of atomic captures
[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 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
78 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
79 #include "common.h"
80
81 #if HAVE_LIBXPM
82 #include "pixmaps/pixmaps.h"
83 #else
84 #include "bitmaps/bitmaps.h"
85 #endif
86
87 #include "frontend.h"
88 #include "backend.h"
89 #include "xevalgraph.h"
90 #include "board.h"
91 #include "menus.h"
92 #include "dialogs.h"
93 #include "gettext.h"
94 #include "draw.h"
95
96
97 #ifdef __EMX__
98 #ifndef HAVE_USLEEP
99 #define HAVE_USLEEP
100 #endif
101 #define usleep(t)   _sleep2(((t)+500)/1000)
102 #endif
103
104 #ifdef ENABLE_NLS
105 # define  _(s) gettext (s)
106 # define N_(s) gettext_noop (s)
107 #else
108 # define  _(s) (s)
109 # define N_(s)  s
110 #endif
111
112 #define SOLID 0
113 #define OUTLINE 1
114 Boolean cairoAnimate;
115 Option *currBoard;
116 cairo_surface_t *csBoardWindow;
117 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
118 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
119 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
120 static cairo_surface_t *pngBoardBitmap[2];
121 int useTexture, textureW[2], textureH[2];
122
123 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
124 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
125
126 #define White(piece) ((int)(piece) < (int)BlackPawn)
127
128 struct {
129   int x1, x2, y1, y2;
130 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
131
132 static int dual = 0;
133
134 void
135 SwitchWindow ()
136 {
137     dual = !dual;
138     currBoard = (dual ? &mainOptions[W_BOARD] : &dualOptions[3]);
139     csBoardWindow = DRAWABLE(currBoard);
140 }
141
142 #define BoardSize int
143 void
144 InitDrawingSizes (BoardSize boardSize, int flags)
145 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
146     int boardWidth, boardHeight;
147     int i;
148     static int oldWidth, oldHeight;
149     static VariantClass oldVariant;
150     static int oldMono = -1, oldTwoBoards = 0;
151     extern Widget formWidget;
152
153     if(!formWidget) return;
154
155     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
156     oldTwoBoards = twoBoards;
157
158     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
159     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
160     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
161
162   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
163
164     oldWidth = boardWidth; oldHeight = boardHeight;
165     CreateGrid();
166
167     /*
168      * Inhibit shell resizing.
169      */
170     ResizeBoardWindow(boardWidth, boardHeight, 0);
171
172     DelayedDrag();
173   }
174
175     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
176     // (only for xpm)
177
178   if(gameInfo.variant != oldVariant) { // and only if variant changed
179
180     for(i=0; i<2; i++) {
181         int p;
182         for(p=0; p<=(int)WhiteKing; p++)
183            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
184         if(gameInfo.variant == VariantShogi) {
185            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
186            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
187            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
188            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
189            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
190         }
191 #ifdef GOTHIC
192         if(gameInfo.variant == VariantGothic) {
193            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
194         }
195 #endif
196         if(gameInfo.variant == VariantSChess) {
197            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
198            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
199         }
200     }
201     oldMono = -10; // kludge to force recreation of animation masks
202     oldVariant = gameInfo.variant;
203   }
204   CreateAnimVars();
205   oldMono = appData.monoMode;
206 }
207
208 static void
209 CreatePNGBoard (char *s, int kind)
210 {
211     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
212     if(strstr(s, ".png")) {
213         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
214         if(img) {
215             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
216             textureW[kind] = cairo_image_surface_get_width (img);
217             textureH[kind] = cairo_image_surface_get_height (img);
218         }
219     }
220 }
221
222 char *pngPieceNames[] = // must be in same order as internal piece encoding
223 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner", 
224   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King", 
225   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
226 };
227
228 cairo_surface_t *
229 ConvertPixmap (int color, int piece)
230 {
231   int i, j, stride, f, colcode[10], w, b;
232   char ch[10];
233   cairo_surface_t *res;
234   XpmPieces *p = builtInXpms + 10;
235   char **pixels = p->xpm[piece % BlackPawn][2*color];
236   int *buf;
237   sscanf(pixels[0], "%*d %*d %d", &f);
238   sscanf(appData.whitePieceColor+1, "%x", &w);
239   sscanf(appData.blackPieceColor+1, "%x", &b);
240   res = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, p->size, p->size);
241   stride = cairo_image_surface_get_stride(res);
242   buf = (int *) cairo_image_surface_get_data(res);
243   for(i=0; i<f; i++) {
244     ch[i] = pixels[i+1][0];
245     colcode[i] = 0;
246     if(strstr(pixels[i+1], "black")) colcode[i] = 0xFF000000; // 0xFF000000 + (color ? b : 0);
247     if(strstr(pixels[i+1], "white")) colcode[i] = 0xFFFFFFCC; // 0xFF000000 + w;
248   }
249   for(i=0; i<p->size; i++) {
250     for(j=0; j<p->size; j++) {
251       char c = pixels[i+f+1][j];
252       int k;
253       for(k=0; ch[k] != c && k < f; k++);
254       buf[i*p->size + j] = colcode[k];
255     }
256   }
257   cairo_surface_mark_dirty(res);
258   return res;
259 }
260
261 static void
262 ScaleOnePiece (char *name, int color, int piece)
263 {
264   float w, h;
265   char buf[MSG_SIZ];
266   RsvgHandle *svg=NULL;
267   RsvgDimensionData svg_dimensions;
268   GError **svgerror=NULL;
269   cairo_surface_t *img, *cs;
270   cairo_t *cr;
271
272   g_type_init ();
273
274   if(*appData.svgDirectory) { // try to freshly render svg pieces first, always from file, to supply the source bitmap
275     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", appData.svgDirectory, color ? "Black" : "White", pngPieceNames[piece]);
276
277     if(svg = rsvg_handle_new_from_file(buf,svgerror)) {
278
279       rsvg_handle_get_dimensions(svg, &svg_dimensions);
280       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
281
282       cr = cairo_create(img);
283       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
284       rsvg_handle_render_cairo(svg, cr);
285       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
286         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
287         pngPieceImages[color][piece] = img;
288       }
289       cairo_destroy(cr);
290
291       rsvg_handle_close (svg,NULL);
292     }
293   }
294
295   if((img = pngPieceImages[color][piece]) == NULL) { // if PNG file for this piece was not yet read, read it now and store it
296     if(!*appData.pngDirectory) img = ConvertPixmap(color, piece); else {
297       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pngDirectory, color ? "Black" : "White", pngPieceNames[piece]);
298       img = cairo_image_surface_create_from_png (buf);
299       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) img = ConvertPixmap(color, piece);
300     }
301   }
302   pngPieceImages[color][piece] = img;
303
304
305   // create new bitmap to hold scaled piece image (and remove any old)
306   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
307   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
308   if(piece <= WhiteKing) pngPieceBitmaps[color][piece] = cs;
309
310   // scaled copying of the raw png image
311   cr = cairo_create(cs);
312   w = cairo_image_surface_get_width (img);
313   h = cairo_image_surface_get_height (img);
314   cairo_scale(cr, squareSize/w, squareSize/h);
315   cairo_set_source_surface (cr, img, 0, 0);
316   cairo_paint (cr);
317   cairo_destroy (cr);
318
319   if(!appData.trueColors || !*appData.pngDirectory && !appData.svgDirectory) { // operate on bitmap to color it (king-size hack...)
320     int stride = cairo_image_surface_get_stride(cs)/4;
321     int *buf = (int *) cairo_image_surface_get_data(cs);
322     int i, j, p;
323     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
324     cairo_surface_flush(cs);
325     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
326         int r, a;
327         float f;
328         unsigned int c = buf[i*stride + j];
329         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
330         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
331         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
332         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
333         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
334     }
335     cairo_surface_mark_dirty(cs);
336   }
337 }
338
339 void
340 CreatePNGPieces ()
341 {
342   int p;
343
344   for(p=0; pngPieceNames[p]; p++) {
345     ScaleOnePiece(pngPieceNames[p], 0, p);
346     ScaleOnePiece(pngPieceNames[p], 1, p);
347   }
348 }
349
350 void
351 CreateAnyPieces ()
352 {   // [HGM] taken out of main
353     CreatePNGPieces();
354     CreatePNGBoard(appData.liteBackTextureFile, 1);
355     CreatePNGBoard(appData.darkBackTextureFile, 0);
356 }
357
358 void
359 InitDrawingParams (int reloadPieces)
360 {
361     int i, p;
362     MakeColors();
363     if(reloadPieces)
364     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
365         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
366         pngPieceImages[i][p] = NULL;
367     }
368     CreateAnyPieces();
369 }
370
371 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
372
373 float
374 Color (char *col, int n)
375 {
376   int c;
377   sscanf(col, "#%x", &c);
378   c = c >> 4*n & 255;
379   return c/255.;
380 }
381
382 void
383 SetPen (cairo_t *cr, float w, char *col, int dash)
384 {
385   static const double dotted[] = {4.0, 4.0};
386   static int len  = sizeof(dotted) / sizeof(dotted[0]);
387   cairo_set_line_width (cr, w);
388   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
389   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
390 }
391
392 void DrawSeekAxis( int x, int y, int xTo, int yTo )
393 {
394     cairo_t *cr;
395
396     /* get a cairo_t */
397     cr = cairo_create (csBoardWindow);
398
399     cairo_move_to (cr, x, y);
400     cairo_line_to(cr, xTo, yTo );
401
402     SetPen(cr, 2, "#000000", 0);
403     cairo_stroke(cr);
404
405     /* free memory */
406     cairo_destroy (cr);
407     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
408 }
409
410 void DrawSeekBackground( int left, int top, int right, int bottom )
411 {
412     cairo_t *cr = cairo_create (csBoardWindow);
413
414     cairo_rectangle (cr, left, top, right-left, bottom-top);
415
416     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
417     cairo_fill(cr);
418
419     /* free memory */
420     cairo_destroy (cr);
421     GraphExpose(currBoard, left, top, right-left, bottom-top);
422 }
423
424 void DrawSeekText(char *buf, int x, int y)
425 {
426     cairo_t *cr = cairo_create (csBoardWindow);
427
428     cairo_select_font_face (cr, "Sans",
429                             CAIRO_FONT_SLANT_NORMAL,
430                             CAIRO_FONT_WEIGHT_NORMAL);
431
432     cairo_set_font_size (cr, 12.0);
433
434     cairo_move_to (cr, x, y+4);
435     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
436     cairo_show_text( cr, buf);
437
438     /* free memory */
439     cairo_destroy (cr);
440     GraphExpose(currBoard, x-5, y-10, 60, 15);
441 }
442
443 void DrawSeekDot(int x, int y, int colorNr)
444 {
445     cairo_t *cr = cairo_create (csBoardWindow);
446     int square = colorNr & 0x80;
447     colorNr &= 0x7F;
448
449     if(square)
450         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
451     else
452         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
453
454     SetPen(cr, 2, "#000000", 0);
455     cairo_stroke_preserve(cr);
456     switch (colorNr) {
457       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
458       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
459       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
460     }
461     cairo_fill(cr);
462
463     /* free memory */
464     cairo_destroy (cr);
465     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
466 }
467
468 void
469 InitDrawingHandle (Option *opt)
470 {
471     csBoardWindow = DRAWABLE(opt);
472 }
473
474 void
475 CreateGrid ()
476 {
477     int i, j;
478
479     if (lineGap == 0) return;
480
481     /* [HR] Split this into 2 loops for non-square boards. */
482
483     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
484         gridSegments[i].x1 = 0;
485         gridSegments[i].x2 =
486           lineGap + BOARD_WIDTH * (squareSize + lineGap);
487         gridSegments[i].y1 = gridSegments[i].y2
488           = lineGap / 2 + (i * (squareSize + lineGap));
489     }
490
491     for (j = 0; j < BOARD_WIDTH + 1; j++) {
492         gridSegments[j + i].y1 = 0;
493         gridSegments[j + i].y2 =
494           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
495         gridSegments[j + i].x1 = gridSegments[j + i].x2
496           = lineGap / 2 + (j * (squareSize + lineGap));
497     }
498 }
499
500 void
501 DrawGrid()
502 {
503   /* draws a grid starting around Nx, Ny squares starting at x,y */
504   int i;
505   float odd = (lineGap & 1)/2.;
506   cairo_t *cr;
507
508   /* get a cairo_t */
509   cr = cairo_create (csBoardWindow);
510
511   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
512   SetPen(cr, lineGap, "#000000", 0);
513
514   /* lines in X */
515   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
516     {
517       cairo_move_to (cr, gridSegments[i].x1 + odd, gridSegments[i].y1 + odd);
518       cairo_line_to (cr, gridSegments[i].x2 + odd, gridSegments[i].y2 + odd);
519       cairo_stroke (cr);
520     }
521
522   /* free memory */
523   cairo_destroy (cr);
524
525   return;
526 }
527
528 void
529 DrawBorder (int x, int y, int type, int odd)
530 {
531     cairo_t *cr;
532     char *col;
533
534     switch(type) {
535         case 0: col = "#000000"; break;
536         case 1: col = appData.highlightSquareColor; break;
537         case 2: col = appData.premoveHighlightColor; break;
538     }
539     cr = cairo_create(csBoardWindow);
540     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
541     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
542     SetPen(cr, lineGap, col, 0);
543     cairo_stroke(cr);
544     cairo_destroy(cr);
545     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
546 }
547
548 static int
549 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
550 {
551     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
552     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
553     *x0 = 0; *y0 = 0;
554     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
555     if(textureW[kind] < W*squareSize)
556         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
557     else
558         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
559     if(textureH[kind] < H*squareSize)
560         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
561     else
562         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
563     return 1;
564 }
565
566 void
567 DrawLogo (Option *opt, void *logo)
568 {
569     cairo_surface_t *img;
570     cairo_t *cr;
571     int w, h;
572
573     if(!logo || !opt) return;
574     img = cairo_image_surface_create_from_png (logo);
575     w = cairo_image_surface_get_width (img);
576     h = cairo_image_surface_get_height (img);
577     cr = cairo_create(DRAWABLE(opt));
578     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
579     cairo_set_source_surface (cr, img, 0, 0);
580     cairo_paint (cr);
581     cairo_destroy (cr);
582     cairo_surface_destroy (img);
583     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
584 }
585
586 static void
587 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
588 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
589     int x0, y0;
590     cairo_t *cr;
591
592     cr = cairo_create (dest);
593
594     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
595             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
596             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
597             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
598             cairo_fill (cr);
599             cairo_destroy (cr);
600     } else { // evenly colored squares
601         char *col;
602         switch (color) {
603           case 0: col = appData.darkSquareColor; break;
604           case 1: col = appData.lightSquareColor; break;
605           case 2: col = "#000000"; break;
606         }
607         SetPen(cr, 2.0, col, 0);
608         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
609         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
610         cairo_fill (cr);
611         cairo_destroy (cr);
612     }
613 }
614
615 static void
616 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
617 {
618     int kind, p = piece;
619     cairo_t *cr;
620
621     if ((int)piece < (int) BlackPawn) {
622         kind = 0;
623     } else {
624         kind = 1;
625         piece -= BlackPawn;
626     }
627     if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
628     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
629     cr = cairo_create (dest);
630     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
631     cairo_paint(cr);
632     cairo_destroy (cr);
633 }
634
635 void
636 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
637 {
638         cairo_t *cr;
639
640         cr = cairo_create(cs);
641         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
642         if(appData.monoMode) {
643             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
644             cairo_stroke_preserve(cr);
645             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
646         } else {
647             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
648         }
649         cairo_fill(cr);
650
651         cairo_destroy(cr);
652 }
653
654 void
655 DrawDot (int marker, int x, int y, int r)
656 { // used for atomic captures; no need to draw on backup
657   DoDrawDot(csBoardWindow, marker, x, y, r);
658   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
659 }
660
661 void
662 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
663 {   // basic front-end board-draw function: takes care of everything that can be in square:
664     // piece, background, coordinate/count, marker dot
665     cairo_t *cr;
666
667     if (piece == EmptySquare) {
668         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
669     } else {
670         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
671     }
672
673     if(align) { // square carries inscription (coord or piece count)
674         int xx = x, yy = y;
675         cairo_text_extents_t te;
676
677         cr = cairo_create (csBoardWindow);
678         cairo_select_font_face (cr, "Sans",
679                     CAIRO_FONT_SLANT_NORMAL,
680                     CAIRO_FONT_WEIGHT_BOLD);
681
682         cairo_set_font_size (cr, squareSize/4);
683         // calculate where it goes
684         cairo_text_extents (cr, string, &te);
685
686         if (align == 1) {
687             xx += squareSize - te.width - te.x_bearing - 1;
688             yy += squareSize - te.height - te.y_bearing - 1;
689         } else if (align == 2) {
690             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
691         } else if (align == 3) {
692             xx += squareSize - te.width -te.x_bearing - 1;
693             yy += -te.y_bearing + 3;
694         } else if (align == 4) {
695             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
696         }
697
698         cairo_move_to (cr, xx-1, yy);
699         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
700         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
701         cairo_show_text (cr, string);
702         cairo_destroy (cr);
703     }
704
705     if(marker) { // print fat marker dot, if requested
706         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
707     }
708 }
709
710 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
711
712 /*      Masks for XPM pieces. Black and white pieces can have
713         different shapes, but in the interest of retaining my
714         sanity pieces must have the same outline on both light
715         and dark squares, and all pieces must use the same
716         background square colors/images.                */
717
718 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
719
720 static void
721 InitAnimState (AnimNr anr)
722 {
723     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
724     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
725     c_animBufs[anr+4] = csBoardWindow;
726     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
727     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
728 }
729
730 void
731 CreateAnimVars ()
732 {
733   InitAnimState(Game);
734   InitAnimState(Player);
735 }
736
737 static void
738 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
739 {
740   static cairo_t *pieceSource;
741   pieceSource = cairo_create (dest);
742   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
743   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
744   else cairo_paint(pieceSource);
745   cairo_destroy (pieceSource);
746 }
747
748 void
749 InsertPiece (AnimNr anr, ChessSquare piece)
750 {
751     CairoOverlayPiece(piece, c_animBufs[anr]);
752 }
753
754 void
755 DrawBlank (AnimNr anr, int x, int y, int startColor)
756 {
757     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
758 }
759
760 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
761                  int srcX, int srcY, int width, int height, int destX, int destY)
762 {
763         cairo_t *cr = cairo_create (c_animBufs[anr+destBuf]);
764         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
765         cairo_rectangle (cr, destX, destY, width, height);
766         cairo_fill (cr);
767         cairo_destroy (cr);
768         if(c_animBufs[anr+destBuf] == csBoardWindow)
769             GraphExpose(currBoard, destX, destY, squareSize, squareSize);
770 }
771
772 void
773 SetDragPiece (AnimNr anr, ChessSquare piece)
774 {
775 }
776
777 /* [AS] Arrow highlighting support */
778
779 void
780 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
781 {
782     cairo_t *cr;
783     int i;
784     cr = cairo_create (cs);
785     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
786     for (i=0;i<nr;i++) {
787         cairo_line_to(cr, arrow[i].x, arrow[i].y);
788     }
789     if(appData.monoMode) { // should we always outline arrow?
790         cairo_line_to(cr, arrow[0].x, arrow[0].y);
791         SetPen(cr, 2, "#000000", 0);
792         cairo_stroke_preserve(cr);
793     }
794     SetPen(cr, 2, appData.highlightSquareColor, 0);
795     cairo_fill(cr);
796
797     /* free memory */
798     cairo_destroy (cr);
799 }
800
801 void
802 DrawPolygon (Pnt arrow[], int nr)
803 {
804     DoDrawPolygon(csBoardWindow, arrow, nr);
805 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
806 }
807
808