2 * draw.c -- drawing routines for XBoard
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
10 * The following terms apply to Digital Equipment Corporation's copyright
12 * ------------------------------------------------------------------------
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.
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
30 * ------------------------------------------------------------------------
32 * The following terms apply to the enhanced version of XBoard
33 * distributed by the Free Software Foundation:
34 * ------------------------------------------------------------------------
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.
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.
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/. *
49 *------------------------------------------------------------------------
50 ** See the file ChangeLog for a revision history. */
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>
65 #else /* not STDC_HEADERS */
66 extern char *getenv();
69 # else /* not HAVE_STRING_H */
71 # endif /* not HAVE_STRING_H */
72 #endif /* not STDC_HEADERS */
84 #include "evalgraph.h"
93 #define usleep(t) _sleep2(((t)+500)/1000)
97 # define _(s) gettext (s)
98 # define N_(s) gettext_noop (s)
106 Boolean cairoAnimate;
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];
116 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
117 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
119 #define White(piece) ((int)(piece) < (int)BlackPawn)
121 char svgDir[MSG_SIZ] = SVGDIR;
123 char *crWhite = "#FFFFB0";
124 char *crBlack = "#AD5D3D";
128 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
131 SwitchWindow (int main)
133 currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
134 csBoardWindow = DRAWABLE(currBoard);
138 SelectPieces(VariantClass v)
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
154 if(v == VariantGothic) {
155 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
158 if(v == VariantSChess) {
159 pngPieceBitmaps[i][(int)WhiteAngel] = pngPieceBitmaps2[i][(int)WhiteFalcon];
160 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
162 if(v == VariantChuChess) {
163 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
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];
178 #define BoardSize int
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;
187 if(!mainOptions[W_BOARD].handle) return;
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;
197 oldNrOfFiles = BOARD_WIDTH;
199 if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
200 oldTwoBoards = twoBoards;
202 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
203 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
204 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
206 if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
208 oldWidth = boardWidth; oldHeight = boardHeight;
210 CreateAnyPieces(0); // redo texture scaling
213 * Inhibit shell resizing.
215 ResizeBoardWindow(boardWidth, boardHeight, 0);
220 // [HGM] pieces: tailor piece bitmaps to needs of specific variant
223 if(gameInfo.variant != oldVariant) { // and only if variant changed
225 SelectPieces(gameInfo.variant);
227 oldVariant = gameInfo.variant;
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);
244 CreatePNGBoard (char *s, int kind)
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);
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);
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
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]++;
271 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
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);
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
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
300 LoadSVG (char *dir, int color, int piece, int retry)
303 RsvgHandle *svg=svgPieces[color][piece];
304 RsvgDimensionData svg_dimensions;
305 GError *svgerror=NULL;
306 cairo_surface_t *img;
308 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
310 if(!name) return NULL;
312 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
314 if(svg || *dir && (svg = rsvg_handle_new_from_file(buf, &svgerror))) {
316 rsvg_handle_get_dimensions(svg, &svg_dimensions);
317 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
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;
330 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteNothing) // pieces that are only different in kanji sets
331 return LoadSVG(dir, color, piece, 1);
333 g_error_free(svgerror);
338 ScaleOnePiece (int color, int piece)
342 cairo_surface_t *img, *cs;
347 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
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;
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
369 img = pngPieceImages[color][piece];
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);
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);
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);
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++) {
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
407 cairo_surface_mark_dirty(cs);
416 for(p=0; pngPieceNames[p]; p++) {
420 SelectPieces(gameInfo.variant);
424 CreateAnyPieces (int p)
425 { // [HGM] taken out of main
426 if(p) CreatePNGPieces();
427 CreatePNGBoard(appData.liteBackTextureFile, 1);
428 CreatePNGBoard(appData.darkBackTextureFile, 0);
432 InitDrawingParams (int 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;
445 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
448 Color (char *col, int n)
451 sscanf(col, "#%x", &c);
457 SetPen (cairo_t *cr, float w, char *col, int dash)
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);
466 void DrawSeekAxis( int x, int y, int xTo, int yTo )
471 cr = cairo_create (csBoardWindow);
473 cairo_move_to (cr, x, y);
474 cairo_line_to(cr, xTo, yTo );
476 SetPen(cr, 2, "#000000", 0);
481 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
484 void DrawSeekBackground( int left, int top, int right, int bottom )
486 cairo_t *cr = cairo_create (csBoardWindow);
488 cairo_rectangle (cr, left, top, right-left, bottom-top);
490 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
495 GraphExpose(currBoard, left, top, right-left, bottom-top);
498 void DrawSeekText(char *buf, int x, int y)
500 cairo_t *cr = cairo_create (csBoardWindow);
502 cairo_select_font_face (cr, "Sans",
503 CAIRO_FONT_SLANT_NORMAL,
504 CAIRO_FONT_WEIGHT_NORMAL);
506 cairo_set_font_size (cr, 12.0);
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);
514 GraphExpose(currBoard, x-5, y-10, 60, 15);
517 void DrawSeekDot(int x, int y, int colorNr)
519 cairo_t *cr = cairo_create (csBoardWindow);
520 int square = colorNr & 0x80;
524 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
526 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
528 SetPen(cr, 2, "#000000", 0);
529 cairo_stroke_preserve(cr);
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;
539 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
543 InitDrawingHandle (Option *opt)
545 csBoardWindow = DRAWABLE(opt);
553 if (lineGap == 0) return;
555 /* [HR] Split this into 2 loops for non-square boards. */
557 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
558 gridSegments[i].x1 = 0;
560 lineGap + BOARD_WIDTH * (squareSize + lineGap);
561 gridSegments[i].y1 = gridSegments[i].y2
562 = lineGap / 2 + (i * (squareSize + lineGap));
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));
577 /* draws a grid starting around Nx, Ny squares starting at x,y */
579 float odd = (lineGap & 1)/2.;
583 cr = cairo_create (csBoardWindow);
585 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
586 SetPen(cr, lineGap, "#000000", 0);
589 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
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);
604 DrawBorder (int x, int y, int type, int odd)
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
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);
621 GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
625 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
627 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
628 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
630 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
631 if(textureW[kind] < W*squareSize)
632 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
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);
638 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
643 DrawLogo (Option *opt, void *logo)
645 cairo_surface_t *img;
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);
659 cairo_surface_destroy (img);
660 GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
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
669 cr = cairo_create (dest);
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);
677 } else { // evenly colored squares
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
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);
694 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
699 if ((int)piece < (int) BlackPawn) {
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);
713 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
716 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
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);
727 SetPen(cr, 2, markerColor[marker-1], 0);
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);
742 DrawUnicode (char *string, int x, int y, char id, int flip)
744 // cairo_text_extents_t te;
748 PangoFontDescription *desc;
749 char fontName[MSG_SIZ];
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);
768 DrawText (char *string, int x, int y, int align)
771 cairo_text_extents_t te;
774 cr = cairo_create (csBoardWindow);
775 cairo_select_font_face (cr, "Sans",
776 CAIRO_FONT_SLANT_NORMAL,
777 CAIRO_FONT_WEIGHT_BOLD);
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);
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;
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);
803 InscribeKanji (ChessSquare piece, int x, int y)
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;
811 while(piece > WhitePawn) {
812 if(*p++ == NULLCHAR) {
813 if(n != WhiteKing) return;
818 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
822 for(q=buf; (*++q & 0xC0) == 0x80;);
824 DrawUnicode(buf, x, y, PieceToChar(n), flip);
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
832 if (piece == EmptySquare) {
833 BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
835 pngDrawPiece(csBoardWindow, piece, square_color, x, y);
836 if(appData.inscriptions[0]) InscribeKanji(piece, x, y);
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)
844 if(marker) { // print fat marker dot, if requested
845 DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
849 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
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. */
857 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
860 InitAnimState (AnimNr anr)
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);
873 InitAnimState(Player);
877 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
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);
888 InsertPiece (AnimNr anr, ChessSquare piece)
890 CairoOverlayPiece(piece, c_animBufs[anr]);
894 DrawBlank (AnimNr anr, int x, int y, int startColor)
896 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
899 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
900 int srcX, int srcY, int width, int height, int destX, int destY)
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);
909 if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
910 GraphExpose(currBoard, destX, destY, width, height);
914 SetDragPiece (AnimNr anr, ChessSquare piece)
918 /* [AS] Arrow highlighting support */
921 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
925 cr = cairo_create (cs);
926 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
928 cairo_line_to(cr, arrow[i].x, arrow[i].y);
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);
935 SetPen(cr, 2, appData.highlightSquareColor, 0);
943 DrawPolygon (Pnt arrow[], int nr)
945 DoDrawPolygon(csBoardWindow, arrow, nr);
946 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
949 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
952 ChoosePen(cairo_t *cr, int i)
956 SetPen(cr, 1.0, "#000000", 0);
959 SetPen(cr, 1.0, "#A0A0A0", 1);
962 SetPen(cr, 1.0, "#0000FF", 1);
965 SetPen(cr, 3.0, crWhite, 0);
968 SetPen(cr, 3.0, crBlack, 0);
971 SetPen(cr, 3.0, "#E0E0F0", 0);
976 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
978 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
980 static int curX, curY;
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);
992 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
996 // front-end wrapper for drawing functions to do rectangles
998 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1002 cr = cairo_create (DRAWABLE(disp));
1003 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1004 cairo_rectangle (cr, left, top, right-left, bottom-top);
1007 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1008 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1009 case 2: ChoosePen(cr, PEN_BACKGD); break;
1015 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1016 ChoosePen(cr, PEN_BLACK);
1023 // front-end wrapper for putting text in graph
1025 DrawEvalText (char *buf, int cbBuf, int y)
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));
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);
1038 cairo_text_extents (cr, buf, &extents);
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);