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