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