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