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;
223 CreatePNGPieces(appData.pieceDirectory);
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) { // failed! If -pid name starts with "sub_" we try to load the piece from the parent directory
356 safeStrCpy(buf, dir, MSG_SIZ);
357 while((q = strchr(p, '/'))) p = q + 1;
358 if(!strncmp(p, "sub_", 4)) {
359 if(p == buf) safeStrCpy(buf, ".", MSG_SIZ); else p[-1] = NULLCHAR; // strip last directory off path
360 return LoadSVG(buf, color, piece, retry);
363 if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
364 snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
365 svg = rsvg_handle_new_from_file(buf, &svgerror);
370 rsvg_handle_get_dimensions(svg, &svg_dimensions);
371 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
373 cr = cairo_create(img);
374 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
375 rsvg_handle_render_cairo(svg, cr);
376 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
377 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
378 pngPieceImages[color][piece] = img;
384 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
385 return LoadSVG(dir, color, piece, 1);
387 g_error_free(svgerror);
392 ScaleOnePiece (int color, int piece, char *pieceDir)
396 cairo_surface_t *img, *cs;
401 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
403 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
404 if(*pieceDir) { // user specified piece directory
405 snprintf(buf, MSG_SIZ, "%s/%s%s.png", pieceDir, color ? "Black" : "White", pngPieceNames[piece]);
406 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
407 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
408 svgPieces[color][piece] = LoadSVG(pieceDir, color, piece, 0); // so try if he has svg there
409 } else pngPieceImages[color][piece] = img;
413 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
414 static int warned = 0;
415 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
416 && !warned && strcmp(pngPieceNames[piece], "Tile")) { // but do not complain about missing 'Tile'
417 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
418 printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
419 DisplayError(msg, 0);
420 warned = 1; // prevent error message being repeated for each piece type
424 img = pngPieceImages[color][piece];
426 // create new bitmap to hold scaled piece image (and remove any old)
427 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
428 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
432 // scaled copying of the raw png image
433 cr = cairo_create(cs);
434 w = cairo_image_surface_get_width (img);
435 h = cairo_image_surface_get_height (img);
436 cairo_scale(cr, squareSize/w, squareSize/h);
437 cairo_set_source_surface (cr, img, 0, 0);
441 if(!appData.trueColors || !*pieceDir) { // operate on bitmap to color it (king-size hack...)
442 int stride = cairo_image_surface_get_stride(cs)/4;
443 int *buf = (int *) cairo_image_surface_get_data(cs);
445 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
446 cairo_surface_flush(cs);
447 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
450 unsigned int c = buf[i*stride + j];
451 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
452 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
453 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
454 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
455 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
456 if(appData.monoMode) {
457 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
458 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
459 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
462 cairo_surface_mark_dirty(cs);
467 CreatePNGPieces (char *pieceDir)
470 for(p=0; pngPieceNames[p]; p++) {
471 ScaleOnePiece(0, p, pieceDir);
472 ScaleOnePiece(1, p, pieceDir);
474 SelectPieces(gameInfo.variant);
478 CreateAnyPieces (int p)
479 { // [HGM] taken out of main
480 if(p) CreatePNGPieces(appData.pieceDirectory);
481 CreatePNGBoard(appData.liteBackTextureFile, 1);
482 CreatePNGBoard(appData.darkBackTextureFile, 0);
489 for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
490 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
491 pngPieceImages[i][p] = NULL;
492 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
493 svgPieces[i][p] = NULL;
498 InitDrawingParams (int reloadPieces)
500 if(reloadPieces) ClearPieces();
505 Preview (int n, char *s)
507 static Boolean changed[4];
510 case 0: // restore true setting
511 if(changed[3]) ClearPieces();
512 CreateAnyPieces(changed[3]); // recomputes textures and (optionally) pieces
513 for(n=0; n<4; n++) changed[n] = FALSE;
517 CreatePNGBoard(s, n-1);
524 DrawPosition(TRUE, NULL);
527 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
530 Color (char *col, int n)
533 sscanf(col, "#%x", &c);
539 SetPen (cairo_t *cr, float w, char *col, int dash)
541 static const double dotted[] = {4.0, 4.0};
542 static int len = sizeof(dotted) / sizeof(dotted[0]);
543 cairo_set_line_width (cr, w);
544 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
545 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
548 void DrawSeekAxis( int x, int y, int xTo, int yTo )
553 cr = cairo_create (CsBoardWindow(currBoard));
555 cairo_move_to (cr, x, y);
556 cairo_line_to(cr, xTo, yTo );
558 SetPen(cr, 2, "#000000", 0);
563 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
566 void DrawSeekBackground( int left, int top, int right, int bottom )
568 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
570 cairo_rectangle (cr, left, top, right-left, bottom-top);
572 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
577 GraphExpose(currBoard, left, top, right-left, bottom-top);
580 void DrawSeekText(char *buf, int x, int y)
582 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
584 cairo_select_font_face (cr, "Sans",
585 CAIRO_FONT_SLANT_NORMAL,
586 CAIRO_FONT_WEIGHT_NORMAL);
588 cairo_set_font_size (cr, 12.0);
590 cairo_move_to (cr, x, y+4);
591 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
592 cairo_show_text( cr, buf);
596 GraphExpose(currBoard, x-5, y-10, 60, 15);
599 void DrawSeekDot(int x, int y, int colorNr)
601 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
602 int square = colorNr & 0x80;
606 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
608 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
610 SetPen(cr, 2, "#000000", 0);
611 cairo_stroke_preserve(cr);
613 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
614 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
615 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
621 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
625 InitDrawingHandle (Option *opt)
627 // CsBoardWindow = DRAWABLE(opt);
636 if (lineGap == 0) return;
638 /* [HR] Split this into 2 loops for non-square boards. */
640 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
641 gridSegments[i].x1 = 0;
643 lineGap + BOARD_WIDTH * (squareSize + lineGap);
644 gridSegments[i].y1 = gridSegments[i].y2
645 = lineGap / 2 + (i * (squareSize + lineGap));
648 for (j = 0; j < BOARD_WIDTH + 1; j++) {
649 gridSegments[j + i].y1 = 0;
650 gridSegments[j + i].y2 =
651 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
652 gridSegments[j + i].x1 = gridSegments[j + i].x2
653 = lineGap / 2 + (j * (squareSize + lineGap));
660 /* draws a grid starting around Nx, Ny squares starting at x,y */
662 float odd = (lineGap & 1)/2.;
666 cr = cairo_create (CsBoardWindow(currBoard));
668 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
669 SetPen(cr, lineGap, "#000000", 0);
672 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
674 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
675 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
676 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
687 DrawBorder (int x, int y, int type, int odd)
693 case 0: col = "#000000"; break;
694 case 1: col = appData.highlightSquareColor; break;
695 case 2: col = appData.premoveHighlightColor; break;
696 default: col = "#808080"; break; // cannot happen
698 cr = cairo_create(CsBoardWindow(currBoard));
699 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
700 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
701 SetPen(cr, lineGap, col, 0);
704 // GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
708 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
710 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
711 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
713 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
714 if(modV[kind] > 0) nx %= modH[kind], ny %= modV[kind]; // tile fixed-format board periodically to extend it
715 if(textureW[kind] < W*squareSize)
716 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
718 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
719 if(textureH[kind] < H*squareSize)
720 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
722 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
727 DrawLogo (Option *opt, void *logo)
729 cairo_surface_t *img;
734 cr = cairo_create(CsBoardWindow(opt));
735 cairo_rectangle (cr, 0, 0, opt->max, opt->value);
736 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
737 cairo_fill(cr); // paint background in case logo does not exist
739 img = cairo_image_surface_create_from_png (logo);
740 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
741 w = cairo_image_surface_get_width (img);
742 h = cairo_image_surface_get_height (img);
743 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
744 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
745 cairo_set_source_surface (cr, img, 0, 0);
748 cairo_surface_destroy (img);
751 GraphExpose(opt, 0, 0, opt->max, opt->value);
755 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
756 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
760 cr = cairo_create (dest);
762 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
763 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
764 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
765 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
768 } else { // evenly colored squares
771 case 0: col = appData.darkSquareColor; break;
772 case 1: col = appData.lightSquareColor; break;
773 case 2: col = "#000000"; break;
774 default: col = "#808080"; break; // cannot happen
776 SetPen(cr, 2.0, col, 0);
777 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
778 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
785 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
790 if ((int)piece < (int) BlackPawn) {
796 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
797 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
798 cr = cairo_create (dest);
799 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
804 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
807 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
811 cr = cairo_create(cs);
812 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
813 if(appData.monoMode) {
814 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
815 cairo_stroke_preserve(cr);
816 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
818 SetPen(cr, 2, markerColor[marker-1], 0);
826 DrawDot (int marker, int x, int y, int r)
827 { // used for atomic captures; no need to draw on backup
828 DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
829 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
833 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
835 // cairo_text_extents_t te;
839 PangoFontDescription *desc;
841 char fontName[MSG_SIZ];
843 cr = cairo_create (canvas);
844 layout = pango_cairo_create_layout(cr);
845 pango_layout_set_text(layout, string, -1);
846 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
847 desc = pango_font_description_from_string(fontName);
848 pango_layout_set_font_description(layout, desc);
849 pango_font_description_free(desc);
850 pango_layout_get_pixel_extents(layout, NULL, &r);
851 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
852 if(s < 0) cairo_rotate(cr, G_PI);
853 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
854 pango_cairo_update_layout(cr, layout);
855 pango_cairo_show_layout(cr, layout);
856 g_object_unref(layout);
861 DrawText (char *string, int x, int y, int align)
864 cairo_text_extents_t te;
867 cr = cairo_create (CsBoardWindow(currBoard));
868 cairo_select_font_face (cr, "Sans",
869 CAIRO_FONT_SLANT_NORMAL,
870 CAIRO_FONT_WEIGHT_BOLD);
872 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
873 // calculate where it goes
874 cairo_text_extents (cr, string, &te);
877 xx += squareSize - te.width - te.x_bearing - 1;
878 yy += squareSize - te.height - te.y_bearing - 1;
879 } else if (align == 2) {
880 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
881 } else if (align == 3) {
882 xx += squareSize - te.width -te.x_bearing - 1;
883 yy += -te.y_bearing + 3;
884 } else if (align == 4) {
885 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
888 cairo_move_to (cr, xx-1, yy);
889 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
890 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
891 cairo_show_text (cr, string);
896 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
898 char *p, *q, buf[10];
899 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
900 if(piece == EmptySquare) return;
901 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
902 p = appData.inscriptions;
904 while(piece > WhitePawn) {
905 if(*p++ == NULLCHAR) {
906 if(n != WhiteKing) return;
911 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
915 for(q=buf; (*++q & 0xC0) == 0x80;);
917 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
921 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
922 { // basic front-end board-draw function: takes care of everything that can be in square:
923 // piece, background, coordinate/count, marker dot
925 if (piece == EmptySquare) {
926 BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
928 pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
929 if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
932 if(align) { // square carries inscription (coord or piece count)
933 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
934 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
937 if(marker) { // print fat marker dot, if requested
938 DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
942 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
944 /* Masks for XPM pieces. Black and white pieces can have
945 different shapes, but in the interest of retaining my
946 sanity pieces must have the same outline on both light
947 and dark squares, and all pieces must use the same
948 background square colors/images. */
950 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
953 InitAnimState (AnimNr anr)
955 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
956 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
957 c_animBufs[anr+4] = CsBoardWindow(currBoard);
958 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
959 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
966 InitAnimState(Player);
970 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
972 static cairo_t *pieceSource;
973 pieceSource = cairo_create (dest);
974 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
975 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
976 else cairo_paint(pieceSource);
977 cairo_destroy (pieceSource);
978 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
982 InsertPiece (AnimNr anr, ChessSquare piece)
984 CairoOverlayPiece(piece, c_animBufs[anr]);
988 DrawBlank (AnimNr anr, int x, int y, int startColor)
990 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
993 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
994 int srcX, int srcY, int width, int height, int destX, int destY)
997 c_animBufs[anr+4] = CsBoardWindow(currBoard);
998 cr = cairo_create (c_animBufs[anr+destBuf]);
999 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
1000 cairo_rectangle (cr, destX, destY, width, height);
1003 if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
1004 GraphExpose(currBoard, destX, destY, width, height);
1008 SetDragPiece (AnimNr anr, ChessSquare piece)
1012 /* [AS] Arrow highlighting support */
1015 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
1019 cr = cairo_create (cs);
1020 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
1021 for (i=0;i<nr;i++) {
1022 cairo_line_to(cr, arrow[i].x, arrow[i].y);
1024 if(appData.monoMode) { // should we always outline arrow?
1025 cairo_line_to(cr, arrow[0].x, arrow[0].y);
1026 SetPen(cr, 2, "#000000", 0);
1027 cairo_stroke_preserve(cr);
1029 SetPen(cr, 2, appData.highlightSquareColor, 0);
1037 DrawPolygon (Pnt arrow[], int nr)
1039 DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1040 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1043 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1046 ChoosePen(cairo_t *cr, int i)
1050 SetPen(cr, 1.0, "#000000", 0);
1053 SetPen(cr, 1.0, "#A0A0A0", 1);
1055 case PEN_BLUEDOTTED:
1056 SetPen(cr, 1.0, "#0000FF", 1);
1059 SetPen(cr, 3.0, crWhite, 0);
1062 SetPen(cr, 3.0, crBlack, 0);
1065 SetPen(cr, 3.0, "#E0E0F0", 0);
1070 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1072 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1074 static int curX, curY;
1076 if(penType != PEN_NONE) {
1077 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1078 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1079 cairo_move_to (cr, curX, curY);
1080 cairo_line_to (cr, x,y);
1081 ChoosePen(cr, penType);
1086 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1090 // front-end wrapper for drawing functions to do rectangles
1092 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1096 cr = cairo_create (CsBoardWindow(disp));
1097 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1098 cairo_rectangle (cr, left, top, right-left, bottom-top);
1101 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1102 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1103 case 2: ChoosePen(cr, PEN_BACKGD); break;
1109 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1110 ChoosePen(cr, PEN_BLACK);
1117 // front-end wrapper for putting text in graph
1119 DrawEvalText (char *buf, int cbBuf, int y)
1121 // the magic constants 8 and 5 should really be derived from the font size somehow
1122 cairo_text_extents_t extents;
1123 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1125 /* GTK-TODO this has to go into the font-selection */
1126 cairo_select_font_face (cr, "Sans",
1127 CAIRO_FONT_SLANT_NORMAL,
1128 CAIRO_FONT_WEIGHT_NORMAL);
1129 cairo_set_font_size (cr, 12.0);
1132 cairo_text_extents (cr, buf, &extents);
1134 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1135 cairo_text_path (cr, buf);
1136 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1137 cairo_fill_preserve (cr);
1138 cairo_set_source_rgb (cr, 0, 1.0, 0);
1139 cairo_set_line_width (cr, 0.1);