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, 2016 Free
9 * Software Foundation, Inc.
11 * The following terms apply to Digital Equipment Corporation's copyright
13 * ------------------------------------------------------------------------
16 * Permission to use, copy, modify, and distribute this software and its
17 * documentation for any purpose and without fee is hereby granted,
18 * provided that the above copyright notice appear in all copies and that
19 * both that copyright notice and this permission notice appear in
20 * supporting documentation, and that the name of Digital not be
21 * used in advertising or publicity pertaining to distribution of the
22 * software without specific, written prior permission.
24 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
25 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
26 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
27 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
28 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
29 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31 * ------------------------------------------------------------------------
33 * The following terms apply to the enhanced version of XBoard
34 * distributed by the Free Software Foundation:
35 * ------------------------------------------------------------------------
37 * GNU XBoard is free software: you can redistribute it and/or modify
38 * it under the terms of the GNU General Public License as published by
39 * the Free Software Foundation, either version 3 of the License, or (at
40 * your option) any later version.
42 * GNU XBoard is distributed in the hope that it will be useful, but
43 * WITHOUT ANY WARRANTY; without even the implied warranty of
44 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45 * General Public License for more details.
47 * You should have received a copy of the GNU General Public License
48 * along with this program. If not, see http://www.gnu.org/licenses/. *
50 *------------------------------------------------------------------------
51 ** See the file ChangeLog for a revision history. */
57 #include <cairo/cairo.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]; // 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]; // scaled pieces in store
112 static RsvgHandle *svgPieces[2][(int)BlackPawn]; // 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);
139 NewCanvas (Option *graph)
142 int w = graph->max, h = graph->value;
143 if(graph->choice) cairo_surface_destroy((cairo_surface_t *) graph->choice);
144 graph->choice = (char**) cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
145 // paint white, to prevent weirdness when people maximize window and drag pieces over space next to board
146 cr = cairo_create ((cairo_surface_t *) graph->choice);
147 cairo_rectangle (cr, 0, 0, w, h);
148 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
151 graph->min &= ~REPLACE;
154 static cairo_surface_t *
155 CsBoardWindow (Option *opt)
156 { // test before every draw event if we need to resize the canvas
157 if(opt->min & REPLACE) NewCanvas(opt);
158 return DRAWABLE(opt);
163 SelectPieces(VariantClass v)
168 for(p=0; p<=(int)WhiteKing; p++)
169 pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
170 if(v == VariantShogi && BOARD_HEIGHT != 7) { // no exceptions in Tori Shogi
171 pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteTokin];
172 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhitePKnight];
173 pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhitePLance];
174 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhitePSilver];
175 pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
176 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteMonarch]; // for Sho Shogi
179 if(v == VariantGothic) {
180 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
183 if(v == VariantSChess) {
184 pngPieceBitmaps[i][(int)WhiteAngel] = pngPieceBitmaps2[i][(int)WhiteFalcon];
185 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
187 if(v == VariantChuChess) {
188 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
190 if(v == VariantChu) {
191 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteClaw];
192 pngPieceBitmaps[i][(int)WhiteClaw] = pngPieceBitmaps2[i][(int)WhiteNightrider];
193 pngPieceBitmaps[i][(int)WhiteUnicorn] = pngPieceBitmaps2[i][(int)WhiteCat];
194 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteSword];
195 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteDagger];
196 pngPieceBitmaps[i][(int)WhiteCat] = pngPieceBitmaps2[i][(int)WhiteUnicorn];
197 pngPieceBitmaps[i][(int)WhiteSword] = pngPieceBitmaps2[i][(int)WhiteSilver];
198 pngPieceBitmaps[i][(int)WhiteDagger] = pngPieceBitmaps2[i][(int)WhiteFalcon];
199 pngPieceBitmaps[i][(int)WhiteMan] = pngPieceBitmaps2[i][(int)WhiteCopper];
200 pngPieceBitmaps[i][(int)WhiteCopper] = pngPieceBitmaps2[i][(int)WhiteMan];
201 pngPieceBitmaps[i][(int)WhiteAxe] = pngPieceBitmaps2[i][(int)WhiteCannon];
202 pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteAxe];
207 #define BoardSize int
209 InitDrawingSizes (BoardSize boardSize, int flags)
210 { // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
211 int boardWidth, boardHeight;
212 static int oldWidth, oldHeight;
213 static VariantClass oldVariant;
214 static int oldTwoBoards = 0, oldNrOfFiles = 0;
216 if(!mainOptions[W_BOARD].handle) return;
218 if(boardSize == -2 && gameInfo.variant != oldVariant
219 && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
220 squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
221 if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
222 squareSize -= lineGap;
226 oldNrOfFiles = BOARD_WIDTH;
228 if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
229 oldTwoBoards = twoBoards;
231 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
232 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
233 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
235 if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
237 oldWidth = boardWidth; oldHeight = boardHeight;
239 CreateAnyPieces(0); // redo texture scaling
242 * Inhibit shell resizing.
244 ResizeBoardWindow(boardWidth, boardHeight, 0);
249 // [HGM] pieces: tailor piece bitmaps to needs of specific variant
252 if(gameInfo.variant != oldVariant) { // and only if variant changed
254 SelectPieces(gameInfo.variant);
256 oldVariant = gameInfo.variant;
262 ExposeRedraw (Option *graph, int x, int y, int w, int h)
263 { // copy a selected part of the buffer bitmap to the display
264 cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
265 cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
266 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
267 cairo_rectangle(cr, x, y, w, h);
272 static int modV[2], modH[2];
275 CreatePNGBoard (char *s, int kind)
278 static float n[2] = { 1., 1. };
279 if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
280 textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
281 if(strstr(s, ".png")) {
282 cairo_surface_t *img = cairo_image_surface_create_from_png (s);
283 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
286 if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
287 if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
288 useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
289 w = textureW[kind] = cairo_image_surface_get_width (img);
290 h = textureH[kind] = cairo_image_surface_get_height (img);
291 n[kind] = 1.; modV[kind] = modH[kind] = -1;
292 while((q = strchr(p+1, '-'))) p = q; // find last '-'
293 if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
294 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
295 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
296 textureH[kind] = (h*BOARD_HEIGHT)/r;
297 n[kind] = r*squareSize/h; // scale to make it fit exactly vertically
298 modV[kind] = r; modH[kind] = f;
300 if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
301 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
303 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
305 if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
306 // create scaled-up copy of the raw png image when it was too small
307 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
308 cairo_t *cr = cairo_create(cs);
309 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
310 // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
311 cairo_scale(cr, n[kind], n[kind]);
312 cairo_set_source_surface (cr, img, 0, 0);
320 char *pngPieceNames[] = // must be in same order as internal piece encoding
321 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
322 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
323 "Sword", "Zebra", "Camel", "Tower", "Wolf", "Hat", "Duck", "Lance", "Dragon", "Gnu", "Cub",
324 "LShield", "Pegasus", "Wizard", "Copper", "Iron", "Viking", "Flag", "Axe", "Dolphin", "Leopard", "Claw",
325 "Left", "Butterfly", "PromoBishop", "PromoRook", "HCrown", "RShield", "Prince", "Phoenix", "Kylin", "Drunk", "Right",
326 "GoldPawn", "GoldKnight", "PromoHorse", "PromoDragon", "GoldLance", "GoldSilver", "HSword", "PromoSword", "PromoHSword", "Princess", "King",
330 char *backupPiece[] = { // pieces that map on other in default theme ("Crown" - "Drunk")
331 "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
332 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Chancellor", NULL,
333 NULL, "Knight", NULL, "Commoner", NULL, NULL, NULL, "Canon", NULL, NULL, NULL,
334 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion", "Elephant"
338 LoadSVG (char *dir, int color, int piece, int retry)
341 RsvgHandle *svg=svgPieces[color][piece];
342 RsvgDimensionData svg_dimensions;
343 GError *svgerror=NULL;
344 cairo_surface_t *img;
346 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
348 if(!name) return NULL;
350 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
353 svg = rsvg_handle_new_from_file(buf, &svgerror);
354 if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
355 snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
356 svg = rsvg_handle_new_from_file(buf, &svgerror);
361 rsvg_handle_get_dimensions(svg, &svg_dimensions);
362 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
364 cr = cairo_create(img);
365 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
366 rsvg_handle_render_cairo(svg, cr);
367 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
368 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
369 pngPieceImages[color][piece] = img;
375 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
376 return LoadSVG(dir, color, piece, 1);
378 g_error_free(svgerror);
383 ScaleOnePiece (int color, int piece)
387 cairo_surface_t *img, *cs;
392 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
394 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
395 if(*appData.pieceDirectory) { // user specified piece directory
396 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
397 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
398 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
399 svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
400 } else pngPieceImages[color][piece] = img;
404 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
405 static int warned = 0;
406 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
407 && !warned && strcmp(pngPieceNames[piece], "Tile")) { // but do not complain about missing 'Tile'
408 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
409 printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
410 DisplayError(msg, 0);
411 warned = 1; // prevent error message being repeated for each piece type
415 img = pngPieceImages[color][piece];
417 // create new bitmap to hold scaled piece image (and remove any old)
418 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
419 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
423 // scaled copying of the raw png image
424 cr = cairo_create(cs);
425 w = cairo_image_surface_get_width (img);
426 h = cairo_image_surface_get_height (img);
427 cairo_scale(cr, squareSize/w, squareSize/h);
428 cairo_set_source_surface (cr, img, 0, 0);
432 if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
433 int stride = cairo_image_surface_get_stride(cs)/4;
434 int *buf = (int *) cairo_image_surface_get_data(cs);
436 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
437 cairo_surface_flush(cs);
438 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
441 unsigned int c = buf[i*stride + j];
442 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
443 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
444 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
445 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
446 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
447 if(appData.monoMode) {
448 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
449 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
450 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
453 cairo_surface_mark_dirty(cs);
462 for(p=0; pngPieceNames[p]; p++) {
466 SelectPieces(gameInfo.variant);
470 CreateAnyPieces (int p)
471 { // [HGM] taken out of main
472 if(p) CreatePNGPieces();
473 CreatePNGBoard(appData.liteBackTextureFile, 1);
474 CreatePNGBoard(appData.darkBackTextureFile, 0);
478 InitDrawingParams (int reloadPieces)
482 for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
483 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
484 pngPieceImages[i][p] = NULL;
485 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
486 svgPieces[i][p] = NULL;
491 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
494 Color (char *col, int n)
497 sscanf(col, "#%x", &c);
503 SetPen (cairo_t *cr, float w, char *col, int dash)
505 static const double dotted[] = {4.0, 4.0};
506 static int len = sizeof(dotted) / sizeof(dotted[0]);
507 cairo_set_line_width (cr, w);
508 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
509 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
512 void DrawSeekAxis( int x, int y, int xTo, int yTo )
517 cr = cairo_create (CsBoardWindow(currBoard));
519 cairo_move_to (cr, x, y);
520 cairo_line_to(cr, xTo, yTo );
522 SetPen(cr, 2, "#000000", 0);
527 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
530 void DrawSeekBackground( int left, int top, int right, int bottom )
532 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
534 cairo_rectangle (cr, left, top, right-left, bottom-top);
536 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
541 GraphExpose(currBoard, left, top, right-left, bottom-top);
544 void DrawSeekText(char *buf, int x, int y)
546 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
548 cairo_select_font_face (cr, "Sans",
549 CAIRO_FONT_SLANT_NORMAL,
550 CAIRO_FONT_WEIGHT_NORMAL);
552 cairo_set_font_size (cr, 12.0);
554 cairo_move_to (cr, x, y+4);
555 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
556 cairo_show_text( cr, buf);
560 GraphExpose(currBoard, x-5, y-10, 60, 15);
563 void DrawSeekDot(int x, int y, int colorNr)
565 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
566 int square = colorNr & 0x80;
570 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
572 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
574 SetPen(cr, 2, "#000000", 0);
575 cairo_stroke_preserve(cr);
577 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
578 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
579 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
585 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
589 InitDrawingHandle (Option *opt)
591 // CsBoardWindow = DRAWABLE(opt);
600 if (lineGap == 0) return;
602 /* [HR] Split this into 2 loops for non-square boards. */
604 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
605 gridSegments[i].x1 = 0;
607 lineGap + BOARD_WIDTH * (squareSize + lineGap);
608 gridSegments[i].y1 = gridSegments[i].y2
609 = lineGap / 2 + (i * (squareSize + lineGap));
612 for (j = 0; j < BOARD_WIDTH + 1; j++) {
613 gridSegments[j + i].y1 = 0;
614 gridSegments[j + i].y2 =
615 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
616 gridSegments[j + i].x1 = gridSegments[j + i].x2
617 = lineGap / 2 + (j * (squareSize + lineGap));
624 /* draws a grid starting around Nx, Ny squares starting at x,y */
626 float odd = (lineGap & 1)/2.;
630 cr = cairo_create (CsBoardWindow(currBoard));
632 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
633 SetPen(cr, lineGap, "#000000", 0);
636 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
638 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
639 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
640 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
651 DrawBorder (int x, int y, int type, int odd)
657 case 0: col = "#000000"; break;
658 case 1: col = appData.highlightSquareColor; break;
659 case 2: col = appData.premoveHighlightColor; break;
660 default: col = "#808080"; break; // cannot happen
662 cr = cairo_create(CsBoardWindow(currBoard));
663 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
664 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
665 SetPen(cr, lineGap, col, 0);
668 // GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
672 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
674 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
675 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
677 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
678 if(modV[kind] > 0) nx %= modH[kind], ny %= modV[kind]; // tile fixed-format board periodically to extend it
679 if(textureW[kind] < W*squareSize)
680 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
682 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
683 if(textureH[kind] < H*squareSize)
684 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
686 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
691 DrawLogo (Option *opt, void *logo)
693 cairo_surface_t *img;
698 cr = cairo_create(CsBoardWindow(opt));
699 cairo_rectangle (cr, 0, 0, opt->max, opt->value);
700 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
701 cairo_fill(cr); // paint background in case logo does not exist
703 img = cairo_image_surface_create_from_png (logo);
704 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
705 w = cairo_image_surface_get_width (img);
706 h = cairo_image_surface_get_height (img);
707 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
708 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
709 cairo_set_source_surface (cr, img, 0, 0);
712 cairo_surface_destroy (img);
715 GraphExpose(opt, 0, 0, opt->max, opt->value);
719 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
720 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
724 cr = cairo_create (dest);
726 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
727 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
728 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
729 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
732 } else { // evenly colored squares
735 case 0: col = appData.darkSquareColor; break;
736 case 1: col = appData.lightSquareColor; break;
737 case 2: col = "#000000"; break;
738 default: col = "#808080"; break; // cannot happen
740 SetPen(cr, 2.0, col, 0);
741 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
742 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
749 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
754 if ((int)piece < (int) BlackPawn) {
760 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
761 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
762 cr = cairo_create (dest);
763 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
768 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
771 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
775 cr = cairo_create(cs);
776 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
777 if(appData.monoMode) {
778 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
779 cairo_stroke_preserve(cr);
780 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
782 SetPen(cr, 2, markerColor[marker-1], 0);
790 DrawDot (int marker, int x, int y, int r)
791 { // used for atomic captures; no need to draw on backup
792 DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
793 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
797 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
799 // cairo_text_extents_t te;
803 PangoFontDescription *desc;
805 char fontName[MSG_SIZ];
807 cr = cairo_create (canvas);
808 layout = pango_cairo_create_layout(cr);
809 pango_layout_set_text(layout, string, -1);
810 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
811 desc = pango_font_description_from_string(fontName);
812 pango_layout_set_font_description(layout, desc);
813 pango_font_description_free(desc);
814 pango_layout_get_pixel_extents(layout, NULL, &r);
815 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
816 if(s < 0) cairo_rotate(cr, G_PI);
817 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
818 pango_cairo_update_layout(cr, layout);
819 pango_cairo_show_layout(cr, layout);
820 g_object_unref(layout);
825 DrawText (char *string, int x, int y, int align)
828 cairo_text_extents_t te;
831 cr = cairo_create (CsBoardWindow(currBoard));
832 cairo_select_font_face (cr, "Sans",
833 CAIRO_FONT_SLANT_NORMAL,
834 CAIRO_FONT_WEIGHT_BOLD);
836 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
837 // calculate where it goes
838 cairo_text_extents (cr, string, &te);
841 xx += squareSize - te.width - te.x_bearing - 1;
842 yy += squareSize - te.height - te.y_bearing - 1;
843 } else if (align == 2) {
844 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
845 } else if (align == 3) {
846 xx += squareSize - te.width -te.x_bearing - 1;
847 yy += -te.y_bearing + 3;
848 } else if (align == 4) {
849 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
852 cairo_move_to (cr, xx-1, yy);
853 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
854 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
855 cairo_show_text (cr, string);
860 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
862 char *p, *q, buf[10];
863 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
864 if(piece == EmptySquare) return;
865 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
866 p = appData.inscriptions;
868 while(piece > WhitePawn) {
869 if(*p++ == NULLCHAR) {
870 if(n != WhiteKing) return;
875 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
879 for(q=buf; (*++q & 0xC0) == 0x80;);
881 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
885 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
886 { // basic front-end board-draw function: takes care of everything that can be in square:
887 // piece, background, coordinate/count, marker dot
889 if (piece == EmptySquare) {
890 BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
892 pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
893 if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
896 if(align) { // square carries inscription (coord or piece count)
897 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
898 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
901 if(marker) { // print fat marker dot, if requested
902 DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
906 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
908 /* Masks for XPM pieces. Black and white pieces can have
909 different shapes, but in the interest of retaining my
910 sanity pieces must have the same outline on both light
911 and dark squares, and all pieces must use the same
912 background square colors/images. */
914 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
917 InitAnimState (AnimNr anr)
919 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
920 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
921 c_animBufs[anr+4] = CsBoardWindow(currBoard);
922 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
923 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
930 InitAnimState(Player);
934 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
936 static cairo_t *pieceSource;
937 pieceSource = cairo_create (dest);
938 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
939 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
940 else cairo_paint(pieceSource);
941 cairo_destroy (pieceSource);
942 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
946 InsertPiece (AnimNr anr, ChessSquare piece)
948 CairoOverlayPiece(piece, c_animBufs[anr]);
952 DrawBlank (AnimNr anr, int x, int y, int startColor)
954 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
957 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
958 int srcX, int srcY, int width, int height, int destX, int destY)
961 c_animBufs[anr+4] = CsBoardWindow(currBoard);
962 cr = cairo_create (c_animBufs[anr+destBuf]);
963 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
964 cairo_rectangle (cr, destX, destY, width, height);
967 if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
968 GraphExpose(currBoard, destX, destY, width, height);
972 SetDragPiece (AnimNr anr, ChessSquare piece)
976 /* [AS] Arrow highlighting support */
979 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
983 cr = cairo_create (cs);
984 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
986 cairo_line_to(cr, arrow[i].x, arrow[i].y);
988 if(appData.monoMode) { // should we always outline arrow?
989 cairo_line_to(cr, arrow[0].x, arrow[0].y);
990 SetPen(cr, 2, "#000000", 0);
991 cairo_stroke_preserve(cr);
993 SetPen(cr, 2, appData.highlightSquareColor, 0);
1001 DrawPolygon (Pnt arrow[], int nr)
1003 DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1004 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1007 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1010 ChoosePen(cairo_t *cr, int i)
1014 SetPen(cr, 1.0, "#000000", 0);
1017 SetPen(cr, 1.0, "#A0A0A0", 1);
1019 case PEN_BLUEDOTTED:
1020 SetPen(cr, 1.0, "#0000FF", 1);
1023 SetPen(cr, 3.0, crWhite, 0);
1026 SetPen(cr, 3.0, crBlack, 0);
1029 SetPen(cr, 3.0, "#E0E0F0", 0);
1034 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1036 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1038 static int curX, curY;
1040 if(penType != PEN_NONE) {
1041 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1042 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1043 cairo_move_to (cr, curX, curY);
1044 cairo_line_to (cr, x,y);
1045 ChoosePen(cr, penType);
1050 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1054 // front-end wrapper for drawing functions to do rectangles
1056 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1060 cr = cairo_create (CsBoardWindow(disp));
1061 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1062 cairo_rectangle (cr, left, top, right-left, bottom-top);
1065 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1066 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1067 case 2: ChoosePen(cr, PEN_BACKGD); break;
1073 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1074 ChoosePen(cr, PEN_BLACK);
1081 // front-end wrapper for putting text in graph
1083 DrawEvalText (char *buf, int cbBuf, int y)
1085 // the magic constants 8 and 5 should really be derived from the font size somehow
1086 cairo_text_extents_t extents;
1087 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1089 /* GTK-TODO this has to go into the font-selection */
1090 cairo_select_font_face (cr, "Sans",
1091 CAIRO_FONT_SLANT_NORMAL,
1092 CAIRO_FONT_WEIGHT_NORMAL);
1093 cairo_set_font_size (cr, 12.0);
1096 cairo_text_extents (cr, buf, &extents);
1098 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1099 cairo_text_path (cr, buf);
1100 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1101 cairo_fill_preserve (cr);
1102 cairo_set_source_rgb (cr, 0, 1.0, 0);
1103 cairo_set_line_width (cr, 0.1);