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