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