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