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