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