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