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(piece == WhiteKing && kind == appData.jewelled) piece = WhiteZebra;
797 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
798 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
799 cr = cairo_create (dest);
800 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
805 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
808 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
812 cr = cairo_create(cs);
813 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
814 if(appData.monoMode) {
815 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
816 cairo_stroke_preserve(cr);
817 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
819 SetPen(cr, 2, markerColor[marker-1], 0);
827 DrawDot (int marker, int x, int y, int r)
828 { // used for atomic captures; no need to draw on backup
829 DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
830 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
834 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
836 // cairo_text_extents_t te;
840 PangoFontDescription *desc;
842 char fontName[MSG_SIZ];
844 cr = cairo_create (canvas);
845 layout = pango_cairo_create_layout(cr);
846 pango_layout_set_text(layout, string, -1);
847 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
848 desc = pango_font_description_from_string(fontName);
849 pango_layout_set_font_description(layout, desc);
850 pango_font_description_free(desc);
851 pango_layout_get_pixel_extents(layout, NULL, &r);
852 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
853 if(s < 0) cairo_rotate(cr, G_PI);
854 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
855 pango_cairo_update_layout(cr, layout);
856 pango_cairo_show_layout(cr, layout);
857 g_object_unref(layout);
862 DrawText (char *string, int x, int y, int align)
865 cairo_text_extents_t te;
868 cr = cairo_create (CsBoardWindow(currBoard));
869 cairo_select_font_face (cr, "Sans",
870 CAIRO_FONT_SLANT_NORMAL,
871 CAIRO_FONT_WEIGHT_BOLD);
873 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
874 // calculate where it goes
875 cairo_text_extents (cr, string, &te);
878 xx += squareSize - te.width - te.x_bearing - 1;
879 yy += squareSize - te.height - te.y_bearing - 1;
880 } else if (align == 2) {
881 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
882 } else if (align == 3) {
883 xx += squareSize - te.width -te.x_bearing - 1;
884 yy += -te.y_bearing + 3;
885 } else if (align == 4) {
886 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
889 cairo_move_to (cr, xx-1, yy);
890 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
891 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
892 cairo_show_text (cr, string);
897 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
899 char *p, *q, buf[10];
900 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
901 if(piece == EmptySquare) return;
902 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
903 p = appData.inscriptions;
905 while(piece > WhitePawn) {
906 if(*p == '/') p++, piece = n - WhitePBishop; // secondary series
907 if(*p++ == NULLCHAR) {
908 if(n != WhiteKing) return;
913 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
917 for(q=buf; (*++q & 0xC0) == 0x80;);
919 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
923 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
924 { // basic front-end board-draw function: takes care of everything that can be in square:
925 // piece, background, coordinate/count, marker dot
927 if (piece == EmptySquare) {
928 BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
930 pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
931 if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
934 if(align) { // square carries inscription (coord or piece count)
935 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
936 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
939 if(marker) { // print fat marker dot, if requested
940 DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
944 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
946 /* Masks for XPM pieces. Black and white pieces can have
947 different shapes, but in the interest of retaining my
948 sanity pieces must have the same outline on both light
949 and dark squares, and all pieces must use the same
950 background square colors/images. */
952 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
955 InitAnimState (AnimNr anr)
957 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
958 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
959 c_animBufs[anr+4] = CsBoardWindow(currBoard);
960 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
961 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
968 InitAnimState(Player);
972 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
974 static cairo_t *pieceSource;
975 pieceSource = cairo_create (dest);
976 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
977 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
978 else cairo_paint(pieceSource);
979 cairo_destroy (pieceSource);
980 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
984 InsertPiece (AnimNr anr, ChessSquare piece)
986 CairoOverlayPiece(piece, c_animBufs[anr]);
990 DrawBlank (AnimNr anr, int x, int y, int startColor)
992 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
995 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
996 int srcX, int srcY, int width, int height, int destX, int destY)
999 c_animBufs[anr+4] = CsBoardWindow(currBoard);
1000 cr = cairo_create (c_animBufs[anr+destBuf]);
1001 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
1002 cairo_rectangle (cr, destX, destY, width, height);
1005 if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
1006 GraphExpose(currBoard, destX, destY, width, height);
1010 SetDragPiece (AnimNr anr, ChessSquare piece)
1014 /* [AS] Arrow highlighting support */
1017 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
1021 cr = cairo_create (cs);
1022 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
1023 for (i=0;i<nr;i++) {
1024 cairo_line_to(cr, arrow[i].x, arrow[i].y);
1026 if(appData.monoMode) { // should we always outline arrow?
1027 cairo_line_to(cr, arrow[0].x, arrow[0].y);
1028 SetPen(cr, 2, "#000000", 0);
1029 cairo_stroke_preserve(cr);
1031 SetPen(cr, 2, appData.highlightSquareColor, 0);
1039 DrawPolygon (Pnt arrow[], int nr)
1041 DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1042 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1045 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1048 ChoosePen(cairo_t *cr, int i)
1052 SetPen(cr, 1.0, "#000000", 0);
1055 SetPen(cr, 1.0, "#A0A0A0", 1);
1057 case PEN_BLUEDOTTED:
1058 SetPen(cr, 1.0, "#0000FF", 1);
1061 SetPen(cr, 3.0, crWhite, 0);
1064 SetPen(cr, 3.0, crBlack, 0);
1067 SetPen(cr, 3.0, "#E0E0F0", 0);
1072 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1074 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1076 static int curX, curY;
1078 if(penType != PEN_NONE) {
1079 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1080 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1081 cairo_move_to (cr, curX, curY);
1082 cairo_line_to (cr, x,y);
1083 ChoosePen(cr, penType);
1088 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1092 // front-end wrapper for drawing functions to do rectangles
1094 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1098 cr = cairo_create (CsBoardWindow(disp));
1099 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1100 cairo_rectangle (cr, left, top, right-left, bottom-top);
1103 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1104 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1105 case 2: ChoosePen(cr, PEN_BACKGD); break;
1111 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1112 ChoosePen(cr, PEN_BLACK);
1119 // front-end wrapper for putting text in graph
1121 DrawEvalText (char *buf, int cbBuf, int y)
1123 // the magic constants 8 and 5 should really be derived from the font size somehow
1124 cairo_text_extents_t extents;
1125 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1127 /* GTK-TODO this has to go into the font-selection */
1128 cairo_select_font_face (cr, "Sans",
1129 CAIRO_FONT_SLANT_NORMAL,
1130 CAIRO_FONT_WEIGHT_NORMAL);
1131 cairo_set_font_size (cr, 12.0);
1134 cairo_text_extents (cr, buf, &extents);
1136 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1137 cairo_text_path (cr, buf);
1138 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1139 cairo_fill_preserve (cr);
1140 cairo_set_source_rgb (cr, 0, 1.0, 0);
1141 cairo_set_line_width (cr, 0.1);