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