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