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 "Tile", "Tile", "Tile", "Tile", "Tile",
292 "GoldPawn", "Claw", "PromoHorse", "PromoDragon", "GoldLance", "PromoSword", "Prince", "Phoenix", "Kylin", "PromoRook", "PromoHSword",
293 "Dolphin", "Sword", "Leopard", "HSword", "GoldSilver", "Princess", "HCrown", "Knight", "Elephant", "PromoBishop",
294 "Tile", "Tile", "Tile", "Tile", "Tile", "King",
295 "Claw", "GoldKnight", "GoldLance", "GoldSilver", NULL
298 char *backupPiece[] = { "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
299 NULL, NULL, NULL, NULL, NULL,
300 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion" }; // pieces that map on other when not kanji
303 LoadSVG (char *dir, int color, int piece, int retry)
306 RsvgHandle *svg=svgPieces[color][piece];
307 RsvgDimensionData svg_dimensions;
308 GError *svgerror=NULL;
309 cairo_surface_t *img;
311 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
313 if(!name) return NULL;
315 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
318 svg = rsvg_handle_new_from_file(buf, &svgerror);
319 if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
320 snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
321 svg = rsvg_handle_new_from_file(buf, &svgerror);
326 rsvg_handle_get_dimensions(svg, &svg_dimensions);
327 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
329 cr = cairo_create(img);
330 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
331 rsvg_handle_render_cairo(svg, cr);
332 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
333 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
334 pngPieceImages[color][piece] = img;
340 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteNothing) // pieces that are only different in kanji sets
341 return LoadSVG(dir, color, piece, 1);
343 g_error_free(svgerror);
348 ScaleOnePiece (int color, int piece)
352 cairo_surface_t *img, *cs;
357 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
359 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
360 if(*appData.pieceDirectory) { // user specified piece directory
361 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
362 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
363 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
364 svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
365 } else pngPieceImages[color][piece] = img;
369 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
370 static int warned = 0;
371 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
372 && !warned && strcmp(pngPieceNames[piece], "Tile")) { // but do not complain about missing 'Tile'
373 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
374 printf("%s\n", msg); // give up
375 DisplayError(msg, 0);
376 warned = 1; // prevent error message being repeated for each piece type
380 img = pngPieceImages[color][piece];
382 // create new bitmap to hold scaled piece image (and remove any old)
383 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
384 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
388 // scaled copying of the raw png image
389 cr = cairo_create(cs);
390 w = cairo_image_surface_get_width (img);
391 h = cairo_image_surface_get_height (img);
392 cairo_scale(cr, squareSize/w, squareSize/h);
393 cairo_set_source_surface (cr, img, 0, 0);
397 if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
398 int stride = cairo_image_surface_get_stride(cs)/4;
399 int *buf = (int *) cairo_image_surface_get_data(cs);
401 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
402 cairo_surface_flush(cs);
403 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
406 unsigned int c = buf[i*stride + j];
407 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
408 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
409 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
410 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
411 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
412 if(appData.monoMode) {
413 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
414 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
415 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
418 cairo_surface_mark_dirty(cs);
427 for(p=0; pngPieceNames[p]; p++) {
431 SelectPieces(gameInfo.variant);
435 CreateAnyPieces (int p)
436 { // [HGM] taken out of main
437 if(p) CreatePNGPieces();
438 CreatePNGBoard(appData.liteBackTextureFile, 1);
439 CreatePNGBoard(appData.darkBackTextureFile, 0);
443 InitDrawingParams (int reloadPieces)
447 for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
448 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
449 pngPieceImages[i][p] = NULL;
450 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
451 svgPieces[i][p] = NULL;
456 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
459 Color (char *col, int n)
462 sscanf(col, "#%x", &c);
468 SetPen (cairo_t *cr, float w, char *col, int dash)
470 static const double dotted[] = {4.0, 4.0};
471 static int len = sizeof(dotted) / sizeof(dotted[0]);
472 cairo_set_line_width (cr, w);
473 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
474 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
477 void DrawSeekAxis( int x, int y, int xTo, int yTo )
482 cr = cairo_create (csBoardWindow);
484 cairo_move_to (cr, x, y);
485 cairo_line_to(cr, xTo, yTo );
487 SetPen(cr, 2, "#000000", 0);
492 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
495 void DrawSeekBackground( int left, int top, int right, int bottom )
497 cairo_t *cr = cairo_create (csBoardWindow);
499 cairo_rectangle (cr, left, top, right-left, bottom-top);
501 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
506 GraphExpose(currBoard, left, top, right-left, bottom-top);
509 void DrawSeekText(char *buf, int x, int y)
511 cairo_t *cr = cairo_create (csBoardWindow);
513 cairo_select_font_face (cr, "Sans",
514 CAIRO_FONT_SLANT_NORMAL,
515 CAIRO_FONT_WEIGHT_NORMAL);
517 cairo_set_font_size (cr, 12.0);
519 cairo_move_to (cr, x, y+4);
520 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
521 cairo_show_text( cr, buf);
525 GraphExpose(currBoard, x-5, y-10, 60, 15);
528 void DrawSeekDot(int x, int y, int colorNr)
530 cairo_t *cr = cairo_create (csBoardWindow);
531 int square = colorNr & 0x80;
535 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
537 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
539 SetPen(cr, 2, "#000000", 0);
540 cairo_stroke_preserve(cr);
542 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
543 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
544 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
550 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
554 InitDrawingHandle (Option *opt)
556 csBoardWindow = DRAWABLE(opt);
564 if (lineGap == 0) return;
566 /* [HR] Split this into 2 loops for non-square boards. */
568 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
569 gridSegments[i].x1 = 0;
571 lineGap + BOARD_WIDTH * (squareSize + lineGap);
572 gridSegments[i].y1 = gridSegments[i].y2
573 = lineGap / 2 + (i * (squareSize + lineGap));
576 for (j = 0; j < BOARD_WIDTH + 1; j++) {
577 gridSegments[j + i].y1 = 0;
578 gridSegments[j + i].y2 =
579 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
580 gridSegments[j + i].x1 = gridSegments[j + i].x2
581 = lineGap / 2 + (j * (squareSize + lineGap));
588 /* draws a grid starting around Nx, Ny squares starting at x,y */
590 float odd = (lineGap & 1)/2.;
594 cr = cairo_create (csBoardWindow);
596 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
597 SetPen(cr, lineGap, "#000000", 0);
600 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
602 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
603 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
604 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
615 DrawBorder (int x, int y, int type, int odd)
621 case 0: col = "#000000"; break;
622 case 1: col = appData.highlightSquareColor; break;
623 case 2: col = appData.premoveHighlightColor; break;
624 default: col = "#808080"; break; // cannot happen
626 cr = cairo_create(csBoardWindow);
627 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
628 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
629 SetPen(cr, lineGap, col, 0);
632 GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
636 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
638 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
639 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
641 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
642 if(textureW[kind] < W*squareSize)
643 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
645 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
646 if(textureH[kind] < H*squareSize)
647 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
649 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
654 DrawLogo (Option *opt, void *logo)
656 cairo_surface_t *img;
660 if(!logo || !opt) return;
661 img = cairo_image_surface_create_from_png (logo);
662 w = cairo_image_surface_get_width (img);
663 h = cairo_image_surface_get_height (img);
664 cr = cairo_create(DRAWABLE(opt));
665 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
666 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
667 cairo_set_source_surface (cr, img, 0, 0);
670 cairo_surface_destroy (img);
671 GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
675 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
676 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
680 cr = cairo_create (dest);
682 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
683 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
684 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
685 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
688 } else { // evenly colored squares
691 case 0: col = appData.darkSquareColor; break;
692 case 1: col = appData.lightSquareColor; break;
693 case 2: col = "#000000"; break;
694 default: col = "#808080"; break; // cannot happen
696 SetPen(cr, 2.0, col, 0);
697 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
698 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
705 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
710 if ((int)piece < (int) BlackPawn) {
716 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
717 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
718 cr = cairo_create (dest);
719 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
724 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
727 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
731 cr = cairo_create(cs);
732 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
733 if(appData.monoMode) {
734 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
735 cairo_stroke_preserve(cr);
736 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
738 SetPen(cr, 2, markerColor[marker-1], 0);
746 DrawDot (int marker, int x, int y, int r)
747 { // used for atomic captures; no need to draw on backup
748 DoDrawDot(csBoardWindow, marker, x, y, r);
749 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
753 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
755 // cairo_text_extents_t te;
759 PangoFontDescription *desc;
761 char fontName[MSG_SIZ];
763 cr = cairo_create (canvas);
764 layout = pango_cairo_create_layout(cr);
765 pango_layout_set_text(layout, string, -1);
766 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
767 desc = pango_font_description_from_string(fontName);
768 pango_layout_set_font_description(layout, desc);
769 pango_font_description_free(desc);
770 pango_layout_get_pixel_extents(layout, NULL, &r);
771 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
772 if(s < 0) cairo_rotate(cr, G_PI);
773 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
774 pango_cairo_update_layout(cr, layout);
775 pango_cairo_show_layout(cr, layout);
776 g_object_unref(layout);
781 DrawText (char *string, int x, int y, int align)
784 cairo_text_extents_t te;
787 cr = cairo_create (csBoardWindow);
788 cairo_select_font_face (cr, "Sans",
789 CAIRO_FONT_SLANT_NORMAL,
790 CAIRO_FONT_WEIGHT_BOLD);
792 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
793 // calculate where it goes
794 cairo_text_extents (cr, string, &te);
797 xx += squareSize - te.width - te.x_bearing - 1;
798 yy += squareSize - te.height - te.y_bearing - 1;
799 } else if (align == 2) {
800 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
801 } else if (align == 3) {
802 xx += squareSize - te.width -te.x_bearing - 1;
803 yy += -te.y_bearing + 3;
804 } else if (align == 4) {
805 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
808 cairo_move_to (cr, xx-1, yy);
809 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
810 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
811 cairo_show_text (cr, string);
816 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
818 char *p, *q, buf[10];
819 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
820 if(piece == EmptySquare) return;
821 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
822 p = appData.inscriptions;
824 while(piece > WhitePawn) {
825 if(*p++ == NULLCHAR) {
826 if(n != WhiteKing) return;
831 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
835 for(q=buf; (*++q & 0xC0) == 0x80;);
837 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
841 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
842 { // basic front-end board-draw function: takes care of everything that can be in square:
843 // piece, background, coordinate/count, marker dot
845 if (piece == EmptySquare) {
846 BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
848 pngDrawPiece(csBoardWindow, piece, square_color, x, y);
849 if(appData.inscriptions[0]) InscribeKanji(csBoardWindow, piece, x, y);
852 if(align) { // square carries inscription (coord or piece count)
853 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
854 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
857 if(marker) { // print fat marker dot, if requested
858 DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
862 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
864 /* Masks for XPM pieces. Black and white pieces can have
865 different shapes, but in the interest of retaining my
866 sanity pieces must have the same outline on both light
867 and dark squares, and all pieces must use the same
868 background square colors/images. */
870 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
873 InitAnimState (AnimNr anr)
875 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
876 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
877 c_animBufs[anr+4] = csBoardWindow;
878 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
879 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
886 InitAnimState(Player);
890 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
892 static cairo_t *pieceSource;
893 pieceSource = cairo_create (dest);
894 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
895 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
896 else cairo_paint(pieceSource);
897 cairo_destroy (pieceSource);
898 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
902 InsertPiece (AnimNr anr, ChessSquare piece)
904 CairoOverlayPiece(piece, c_animBufs[anr]);
908 DrawBlank (AnimNr anr, int x, int y, int startColor)
910 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
913 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
914 int srcX, int srcY, int width, int height, int destX, int destY)
917 c_animBufs[anr+4] = csBoardWindow;
918 cr = cairo_create (c_animBufs[anr+destBuf]);
919 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
920 cairo_rectangle (cr, destX, destY, width, height);
923 if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
924 GraphExpose(currBoard, destX, destY, width, height);
928 SetDragPiece (AnimNr anr, ChessSquare piece)
932 /* [AS] Arrow highlighting support */
935 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
939 cr = cairo_create (cs);
940 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
942 cairo_line_to(cr, arrow[i].x, arrow[i].y);
944 if(appData.monoMode) { // should we always outline arrow?
945 cairo_line_to(cr, arrow[0].x, arrow[0].y);
946 SetPen(cr, 2, "#000000", 0);
947 cairo_stroke_preserve(cr);
949 SetPen(cr, 2, appData.highlightSquareColor, 0);
957 DrawPolygon (Pnt arrow[], int nr)
959 DoDrawPolygon(csBoardWindow, arrow, nr);
960 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
963 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
966 ChoosePen(cairo_t *cr, int i)
970 SetPen(cr, 1.0, "#000000", 0);
973 SetPen(cr, 1.0, "#A0A0A0", 1);
976 SetPen(cr, 1.0, "#0000FF", 1);
979 SetPen(cr, 3.0, crWhite, 0);
982 SetPen(cr, 3.0, crBlack, 0);
985 SetPen(cr, 3.0, "#E0E0F0", 0);
990 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
992 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
994 static int curX, curY;
996 if(penType != PEN_NONE) {
997 cairo_t *cr = cairo_create(DRAWABLE(disp));
998 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
999 cairo_move_to (cr, curX, curY);
1000 cairo_line_to (cr, x,y);
1001 ChoosePen(cr, penType);
1006 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1010 // front-end wrapper for drawing functions to do rectangles
1012 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1016 cr = cairo_create (DRAWABLE(disp));
1017 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1018 cairo_rectangle (cr, left, top, right-left, bottom-top);
1021 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1022 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1023 case 2: ChoosePen(cr, PEN_BACKGD); break;
1029 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1030 ChoosePen(cr, PEN_BLACK);
1037 // front-end wrapper for putting text in graph
1039 DrawEvalText (char *buf, int cbBuf, int y)
1041 // the magic constants 8 and 5 should really be derived from the font size somehow
1042 cairo_text_extents_t extents;
1043 cairo_t *cr = cairo_create(DRAWABLE(disp));
1045 /* GTK-TODO this has to go into the font-selection */
1046 cairo_select_font_face (cr, "Sans",
1047 CAIRO_FONT_SLANT_NORMAL,
1048 CAIRO_FONT_WEIGHT_NORMAL);
1049 cairo_set_font_size (cr, 12.0);
1052 cairo_text_extents (cr, buf, &extents);
1054 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1055 cairo_text_path (cr, buf);
1056 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1057 cairo_fill_preserve (cr);
1058 cairo_set_source_rgb (cr, 0, 1.0, 0);
1059 cairo_set_line_width (cr, 0.1);