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