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