initial svg rendering
[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 Free Software Foundation, Inc.
9  *
10  * The following terms apply to Digital Equipment Corporation's copyright
11  * interest in XBoard:
12  * ------------------------------------------------------------------------
13  * All Rights Reserved
14  *
15  * Permission to use, copy, modify, and distribute this software and its
16  * documentation for any purpose and without fee is hereby granted,
17  * provided that the above copyright notice appear in all copies and that
18  * both that copyright notice and this permission notice appear in
19  * supporting documentation, and that the name of Digital not be
20  * used in advertising or publicity pertaining to distribution of the
21  * software without specific, written prior permission.
22  *
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29  * SOFTWARE.
30  * ------------------------------------------------------------------------
31  *
32  * The following terms apply to the enhanced version of XBoard
33  * distributed by the Free Software Foundation:
34  * ------------------------------------------------------------------------
35  *
36  * GNU XBoard is free software: you can redistribute it and/or modify
37  * it under the terms of the GNU General Public License as published by
38  * the Free Software Foundation, either version 3 of the License, or (at
39  * your option) any later version.
40  *
41  * GNU XBoard is distributed in the hope that it will be useful, but
42  * WITHOUT ANY WARRANTY; without even the implied warranty of
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44  * General Public License for more details.
45  *
46  * You should have received a copy of the GNU General Public License
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *
48  *
49  *------------------------------------------------------------------------
50  ** See the file ChangeLog for a revision history.  */
51
52 #include "config.h"
53
54 #include <stdio.h>
55 #include <math.h>
56 #include <cairo/cairo.h>
57 #include <cairo/cairo-xlib.h>
58 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
60
61 #if STDC_HEADERS
62 # include <stdlib.h>
63 # include <string.h>
64 #else /* not STDC_HEADERS */
65 extern char *getenv();
66 # if HAVE_STRING_H
67 #  include <string.h>
68 # else /* not HAVE_STRING_H */
69 #  include <strings.h>
70 # endif /* not HAVE_STRING_H */
71 #endif /* not STDC_HEADERS */
72
73 #if ENABLE_NLS
74 #include <locale.h>
75 #endif
76
77
78 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
79 #include "common.h"
80
81 #if HAVE_LIBXPM
82 #include "pixmaps/pixmaps.h"
83 #else
84 #include "bitmaps/bitmaps.h"
85 #endif
86
87 #include "frontend.h"
88 #include "backend.h"
89 #include "xevalgraph.h"
90 #include "board.h"
91 #include "menus.h"
92 #include "dialogs.h"
93 #include "gettext.h"
94 #include "draw.h"
95
96
97 #ifdef __EMX__
98 #ifndef HAVE_USLEEP
99 #define HAVE_USLEEP
100 #endif
101 #define usleep(t)   _sleep2(((t)+500)/1000)
102 #endif
103
104 #ifdef ENABLE_NLS
105 # define  _(s) gettext (s)
106 # define N_(s) gettext_noop (s)
107 #else
108 # define  _(s) (s)
109 # define N_(s)  s
110 #endif
111
112 #define SOLID 0
113 #define OUTLINE 1
114 Boolean cairoAnimate;
115 Option *currBoard;
116 cairo_surface_t *csBoardWindow;
117 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
118 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
119 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
120 static cairo_surface_t *pngBoardBitmap[2];
121 int useTexture, textureW[2], textureH[2];
122
123 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
124 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
125
126 #define White(piece) ((int)(piece) < (int)BlackPawn)
127
128 struct {
129   int x1, x2, y1, y2;
130 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
131
132 static int dual = 0;
133
134 void
135 SwitchWindow ()
136 {
137     dual = !dual;
138     currBoard = (dual ? &mainOptions[W_BOARD] : &dualOptions[3]);
139     csBoardWindow = DRAWABLE(currBoard);
140 }
141
142 #define BoardSize int
143 void
144 InitDrawingSizes (BoardSize boardSize, int flags)
145 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
146     int boardWidth, boardHeight;
147     int i;
148     static int oldWidth, oldHeight;
149     static VariantClass oldVariant;
150     static int oldMono = -1, oldTwoBoards = 0;
151     extern Widget formWidget;
152
153     if(!formWidget) return;
154
155     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
156     oldTwoBoards = twoBoards;
157
158     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
159     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
160     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
161
162   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
163
164     oldWidth = boardWidth; oldHeight = boardHeight;
165     CreateGrid();
166
167     /*
168      * Inhibit shell resizing.
169      */
170     ResizeBoardWindow(boardWidth, boardHeight, 0);
171
172     DelayedDrag();
173   }
174
175     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
176     // (only for xpm)
177
178   if(gameInfo.variant != oldVariant) { // and only if variant changed
179
180     for(i=0; i<2; i++) {
181         int p;
182         for(p=0; p<=(int)WhiteKing; p++)
183            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
184         if(gameInfo.variant == VariantShogi) {
185            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
186            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
187            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
188            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
189            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
190         }
191 #ifdef GOTHIC
192         if(gameInfo.variant == VariantGothic) {
193            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
194         }
195 #endif
196         if(gameInfo.variant == VariantSChess) {
197            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
198            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
199         }
200     }
201     oldMono = -10; // kludge to force recreation of animation masks
202     oldVariant = gameInfo.variant;
203   }
204   CreateAnimVars();
205   oldMono = appData.monoMode;
206 }
207
208 static void
209 CreatePNGBoard (char *s, int kind)
210 {
211     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
212     if(strstr(s, ".png")) {
213         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
214         if(img) {
215             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
216             textureW[kind] = cairo_image_surface_get_width (img);
217             textureH[kind] = cairo_image_surface_get_height (img);
218         }
219     }
220 }
221
222 char *pngPieceNames[] = // must be in same order as internal piece encoding
223 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner", 
224   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King", 
225   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
226 };
227
228 cairo_surface_t *
229 ConvertPixmap (int color, int piece)
230 {
231   int i, j, stride, f, colcode[10], w, b;
232   char ch[10];
233   cairo_surface_t *res;
234   XpmPieces *p = builtInXpms + 10;
235   char **pixels = p->xpm[piece % BlackPawn][2*color];
236   int *buf;
237   sscanf(pixels[0], "%*d %*d %d", &f);
238   sscanf(appData.whitePieceColor+1, "%x", &w);
239   sscanf(appData.blackPieceColor+1, "%x", &b);
240   res = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, p->size, p->size);
241   stride = cairo_image_surface_get_stride(res);
242   buf = (int *) cairo_image_surface_get_data(res);
243   for(i=0; i<f; i++) {
244     ch[i] = pixels[i+1][0];
245     colcode[i] = 0;
246     if(strstr(pixels[i+1], "black")) colcode[i] = 0xFF000000; // 0xFF000000 + (color ? b : 0);
247     if(strstr(pixels[i+1], "white")) colcode[i] = 0xFFFFFFCC; // 0xFF000000 + w;
248   }
249   for(i=0; i<p->size; i++) {
250     for(j=0; j<p->size; j++) {
251       char c = pixels[i+f+1][j];
252       int k;
253       for(k=0; ch[k] != c && k < f; k++);
254       buf[i*p->size + j] = colcode[k];
255     }
256   }
257   cairo_surface_mark_dirty(res);
258   return res;
259 }
260
261 static void
262 ScaleOnePiece (char *name, int color, int piece)
263 {
264   float w, h;
265   char buf[MSG_SIZ];
266   RsvgHandle *svg=NULL;
267   RsvgDimensionData svg_dimensions;
268   GError **svgerror=NULL;
269   cairo_surface_t *img, *cs;
270   cairo_t *cr;
271   int stride = squareSize * 4;
272   double scale;
273
274   g_type_init ();
275
276   if((img = pngPieceImages[color][piece]) == NULL) { // if PNG file for this piece was not yet read, read it now and store it
277     if(!*appData.pngDirectory) img = ConvertPixmap(color, piece); else {
278       snprintf(buf, MSG_SIZ, "%s/%s%s.svg", appData.pngDirectory, color ? "Black" : "White", pngPieceNames[piece]);
279
280       svg = rsvg_handle_new ();
281       svg = rsvg_handle_new_from_file(buf,svgerror);
282
283       rsvg_handle_get_dimensions(svg, &svg_dimensions);
284
285       unsigned char* cairo_data =(unsigned char *) calloc(stride * squareSize, 1);
286       img = cairo_image_surface_create_for_data(cairo_data, CAIRO_FORMAT_ARGB32, squareSize,  squareSize, stride);
287
288       cairo_t *cr_svg = cairo_create(img);
289
290       scale = (double) squareSize/(double) svg_dimensions.height;
291       cairo_scale(cr_svg, scale,scale);
292       cairo_set_antialias (cr_svg, CAIRO_ANTIALIAS_NONE);
293       rsvg_handle_render_cairo(svg, cr_svg);
294
295       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) img = ConvertPixmap(color, piece);
296
297       rsvg_handle_close (svg,NULL);
298     }
299   }
300   pngPieceImages[color][piece] = img;
301
302
303   // create new bitmap to hold scaled piece image (and remove any old)
304   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
305   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
306   if(piece <= WhiteKing) pngPieceBitmaps[color][piece] = cs;
307
308   // scaled copying of the raw png image
309   cr = cairo_create(cs);
310   w = cairo_image_surface_get_width (img);
311   h = cairo_image_surface_get_height (img);
312   cairo_scale(cr, squareSize/w, squareSize/h);
313   cairo_set_source_surface (cr, img, 0, 0);
314   cairo_paint (cr);
315   cairo_destroy (cr);
316
317   { // operate on bitmap to color it (king-size hack...)
318     int stride = cairo_image_surface_get_stride(cs)/4;
319     int *buf = (int *) cairo_image_surface_get_data(cs);
320     int i, j, p;
321     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
322     cairo_surface_flush(cs);
323     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
324         int r, a;
325         float f;
326         unsigned int c = buf[i*stride + j];
327         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
328         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
329         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
330         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
331         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
332     }
333     cairo_surface_mark_dirty(cs);
334   }
335 }
336
337 void
338 CreatePNGPieces ()
339 {
340   int p;
341
342   for(p=0; pngPieceNames[p]; p++) {
343     ScaleOnePiece(pngPieceNames[p], 0, p);
344     ScaleOnePiece(pngPieceNames[p], 1, p);
345   }
346 }
347
348 void
349 CreateAnyPieces ()
350 {   // [HGM] taken out of main
351     CreatePNGPieces();
352     CreatePNGBoard(appData.liteBackTextureFile, 1);
353     CreatePNGBoard(appData.darkBackTextureFile, 0);
354 }
355
356 void
357 InitDrawingParams (int reloadPieces)
358 {
359     int i, p;
360     MakeColors();
361     if(reloadPieces)
362     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
363         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
364         pngPieceImages[i][p] = NULL;
365     }
366     CreateAnyPieces();
367 }
368
369 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
370
371 float
372 Color (char *col, int n)
373 {
374   int c;
375   sscanf(col, "#%x", &c);
376   c = c >> 4*n & 255;
377   return c/255.;
378 }
379
380 void
381 SetPen (cairo_t *cr, float w, char *col, int dash)
382 {
383   static const double dotted[] = {4.0, 4.0};
384   static int len  = sizeof(dotted) / sizeof(dotted[0]);
385   cairo_set_line_width (cr, w);
386   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
387   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
388 }
389
390 void DrawSeekAxis( int x, int y, int xTo, int yTo )
391 {
392     cairo_t *cr;
393
394     /* get a cairo_t */
395     cr = cairo_create (csBoardWindow);
396
397     cairo_move_to (cr, x, y);
398     cairo_line_to(cr, xTo, yTo );
399
400     SetPen(cr, 2, "#000000", 0);
401     cairo_stroke(cr);
402
403     /* free memory */
404     cairo_destroy (cr);
405     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
406 }
407
408 void DrawSeekBackground( int left, int top, int right, int bottom )
409 {
410     cairo_t *cr = cairo_create (csBoardWindow);
411
412     cairo_rectangle (cr, left, top, right-left, bottom-top);
413
414     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
415     cairo_fill(cr);
416
417     /* free memory */
418     cairo_destroy (cr);
419     GraphExpose(currBoard, left, top, right-left, bottom-top);
420 }
421
422 void DrawSeekText(char *buf, int x, int y)
423 {
424     cairo_t *cr = cairo_create (csBoardWindow);
425
426     cairo_select_font_face (cr, "Sans",
427                             CAIRO_FONT_SLANT_NORMAL,
428                             CAIRO_FONT_WEIGHT_NORMAL);
429
430     cairo_set_font_size (cr, 12.0);
431
432     cairo_move_to (cr, x, y+4);
433     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
434     cairo_show_text( cr, buf);
435
436     /* free memory */
437     cairo_destroy (cr);
438     GraphExpose(currBoard, x-5, y-10, 60, 15);
439 }
440
441 void DrawSeekDot(int x, int y, int colorNr)
442 {
443     cairo_t *cr = cairo_create (csBoardWindow);
444     int square = colorNr & 0x80;
445     colorNr &= 0x7F;
446
447     if(square)
448         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
449     else
450         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
451
452     SetPen(cr, 2, "#000000", 0);
453     cairo_stroke_preserve(cr);
454     switch (colorNr) {
455       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
456       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
457       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
458     }
459     cairo_fill(cr);
460
461     /* free memory */
462     cairo_destroy (cr);
463     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
464 }
465
466 void
467 InitDrawingHandle (Option *opt)
468 {
469     csBoardWindow = DRAWABLE(opt);
470 }
471
472 void
473 CreateGrid ()
474 {
475     int i, j;
476
477     if (lineGap == 0) return;
478
479     /* [HR] Split this into 2 loops for non-square boards. */
480
481     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
482         gridSegments[i].x1 = 0;
483         gridSegments[i].x2 =
484           lineGap + BOARD_WIDTH * (squareSize + lineGap);
485         gridSegments[i].y1 = gridSegments[i].y2
486           = lineGap / 2 + (i * (squareSize + lineGap));
487     }
488
489     for (j = 0; j < BOARD_WIDTH + 1; j++) {
490         gridSegments[j + i].y1 = 0;
491         gridSegments[j + i].y2 =
492           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
493         gridSegments[j + i].x1 = gridSegments[j + i].x2
494           = lineGap / 2 + (j * (squareSize + lineGap));
495     }
496 }
497
498 void
499 DrawGrid()
500 {
501   /* draws a grid starting around Nx, Ny squares starting at x,y */
502   int i;
503   cairo_t *cr;
504
505   /* get a cairo_t */
506   cr = cairo_create (csBoardWindow);
507
508   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
509   SetPen(cr, lineGap, "#000000", 0);
510
511   /* lines in X */
512   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
513     {
514       cairo_move_to (cr, gridSegments[i].x1, gridSegments[i].y1);
515       cairo_line_to (cr, gridSegments[i].x2, gridSegments[i].y2);
516       cairo_stroke (cr);
517     }
518
519   /* free memory */
520   cairo_destroy (cr);
521
522   return;
523 }
524
525 void
526 DrawBorder (int x, int y, int type)
527 {
528     cairo_t *cr;
529     char *col;
530
531     switch(type) {
532         case 0: col = "#000000"; break;
533         case 1: col = appData.highlightSquareColor; break;
534         case 2: col = appData.premoveHighlightColor; break;
535     }
536     cr = cairo_create(csBoardWindow);
537     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
538     cairo_rectangle(cr, x, y, squareSize+lineGap, squareSize+lineGap);
539     SetPen(cr, lineGap, col, 0);
540     cairo_stroke(cr);
541     cairo_destroy(cr);
542     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap, squareSize+2*lineGap);
543 }
544
545 static int
546 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
547 {
548     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
549     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
550     *x0 = 0; *y0 = 0;
551     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
552     if(textureW[kind] < W*squareSize)
553         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
554     else
555         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
556     if(textureH[kind] < H*squareSize)
557         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
558     else
559         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
560     return 1;
561 }
562
563 void
564 DrawLogo (Option *opt, void *logo)
565 {
566     cairo_surface_t *img;
567     cairo_t *cr;
568     int w, h;
569
570     if(!logo || !opt) return;
571     img = cairo_image_surface_create_from_png (logo);
572     w = cairo_image_surface_get_width (img);
573     h = cairo_image_surface_get_height (img);
574     cr = cairo_create(DRAWABLE(opt));
575     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
576     cairo_set_source_surface (cr, img, 0, 0);
577     cairo_paint (cr);
578     cairo_destroy (cr);
579     cairo_surface_destroy (img);
580     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
581 }
582
583 static void
584 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
585 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
586     int x0, y0;
587     cairo_t *cr;
588
589     cr = cairo_create (dest);
590
591     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
592             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
593             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
594             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
595             cairo_fill (cr);
596             cairo_destroy (cr);
597     } else { // evenly colored squares
598         char *col;
599         switch (color) {
600           case 0: col = appData.darkSquareColor; break;
601           case 1: col = appData.lightSquareColor; break;
602           case 2: col = "#000000"; break;
603         }
604         SetPen(cr, 2.0, col, 0);
605         cairo_rectangle (cr, x, y, squareSize, squareSize);
606         cairo_fill (cr);
607         cairo_destroy (cr);
608     }
609 }
610
611 static void
612 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
613 {
614     int kind, p = piece;
615     cairo_t *cr;
616
617     if ((int)piece < (int) BlackPawn) {
618         kind = 0;
619     } else {
620         kind = 1;
621         piece -= BlackPawn;
622     }
623     if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
624     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
625     cr = cairo_create (dest);
626     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
627     cairo_paint(cr);
628     cairo_destroy (cr);
629 }
630
631 void
632 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
633 {
634         cairo_t *cr;
635
636         cr = cairo_create(cs);
637         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
638         if(appData.monoMode) {
639             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
640             cairo_stroke_preserve(cr);
641             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
642         } else {
643             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
644         }
645         cairo_fill(cr);
646
647         cairo_destroy(cr);
648 }
649
650 void
651 DrawDot (int marker, int x, int y, int r)
652 { // used for atomic captures; no need to draw on backup
653   DoDrawDot(csBoardWindow, marker, x, y, r);
654 }
655
656 void
657 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
658 {   // basic front-end board-draw function: takes care of everything that can be in square:
659     // piece, background, coordinate/count, marker dot
660     cairo_t *cr;
661
662     if (piece == EmptySquare) {
663         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
664     } else {
665         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
666     }
667
668     if(align) { // square carries inscription (coord or piece count)
669         int xx = x, yy = y;
670         cairo_text_extents_t te;
671
672         cr = cairo_create (csBoardWindow);
673         cairo_select_font_face (cr, "Sans",
674                     CAIRO_FONT_SLANT_NORMAL,
675                     CAIRO_FONT_WEIGHT_BOLD);
676
677         cairo_set_font_size (cr, squareSize/4);
678         // calculate where it goes
679         cairo_text_extents (cr, string, &te);
680
681         if (align == 1) {
682             xx += squareSize - te.width - te.x_bearing - 1;
683             yy += squareSize - te.height - te.y_bearing - 1;
684         } else if (align == 2) {
685             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
686         } else if (align == 3) {
687             xx += squareSize - te.width -te.x_bearing - 1;
688             yy += -te.y_bearing + 3;
689         } else if (align == 4) {
690             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
691         }
692
693         cairo_move_to (cr, xx-1, yy);
694         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
695         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
696         cairo_show_text (cr, string);
697         cairo_destroy (cr);
698     }
699
700     if(marker) { // print fat marker dot, if requested
701         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
702     }
703 }
704
705 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
706
707 /*      Masks for XPM pieces. Black and white pieces can have
708         different shapes, but in the interest of retaining my
709         sanity pieces must have the same outline on both light
710         and dark squares, and all pieces must use the same
711         background square colors/images.                */
712
713 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
714
715 static void
716 InitAnimState (AnimNr anr)
717 {
718     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
719     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
720     c_animBufs[anr+4] = csBoardWindow;
721     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
722     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
723 }
724
725 void
726 CreateAnimVars ()
727 {
728   InitAnimState(Game);
729   InitAnimState(Player);
730 }
731
732 static void
733 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
734 {
735   static cairo_t *pieceSource;
736   pieceSource = cairo_create (dest);
737   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
738   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
739   else cairo_paint(pieceSource);
740   cairo_destroy (pieceSource);
741 }
742
743 void
744 InsertPiece (AnimNr anr, ChessSquare piece)
745 {
746     CairoOverlayPiece(piece, c_animBufs[anr]);
747 }
748
749 void
750 DrawBlank (AnimNr anr, int x, int y, int startColor)
751 {
752     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
753 }
754
755 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
756                  int srcX, int srcY, int width, int height, int destX, int destY)
757 {
758         cairo_t *cr = cairo_create (c_animBufs[anr+destBuf]);
759         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
760         cairo_rectangle (cr, destX, destY, width, height);
761         cairo_fill (cr);
762         cairo_destroy (cr);
763         if(c_animBufs[anr+destBuf] == csBoardWindow)
764             GraphExpose(currBoard, destX, destY, squareSize, squareSize);
765 }
766
767 void
768 SetDragPiece (AnimNr anr, ChessSquare piece)
769 {
770 }
771
772 /* [AS] Arrow highlighting support */
773
774 void
775 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
776 {
777     cairo_t *cr;
778     int i;
779     cr = cairo_create (cs);
780     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
781     for (i=0;i<nr;i++) {
782         cairo_line_to(cr, arrow[i].x, arrow[i].y);
783     }
784     if(appData.monoMode) { // should we always outline arrow?
785         cairo_line_to(cr, arrow[0].x, arrow[0].y);
786         SetPen(cr, 2, "#000000", 0);
787         cairo_stroke_preserve(cr);
788     }
789     SetPen(cr, 2, appData.highlightSquareColor, 0);
790     cairo_fill(cr);
791
792     /* free memory */
793     cairo_destroy (cr);
794 }
795
796 void
797 DrawPolygon (Pnt arrow[], int nr)
798 {
799     DoDrawPolygon(csBoardWindow, arrow, nr);
800 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
801 }
802
803