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