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