1 /*
2  * draw.c -- drawing routines for XBoard
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
9  *
10  * The following terms apply to Digital Equipment Corporation's copyright
11  * interest in XBoard:
12  * ------------------------------------------------------------------------
13  * All Rights Reserved
14  *
15  * Permission to use, copy, modify, and distribute this software and its
16  * documentation for any purpose and without fee is hereby granted,
17  * provided that the above copyright notice appear in all copies and that
18  * both that copyright notice and this permission notice appear in
19  * supporting documentation, and that the name of Digital not be
20  * used in advertising or publicity pertaining to distribution of the
21  * software without specific, written prior permission.
22  *
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29  * SOFTWARE.
30  * ------------------------------------------------------------------------
31  *
32  * The following terms apply to the enhanced version of XBoard
33  * distributed by the Free Software Foundation:
34  * ------------------------------------------------------------------------
35  *
36  * GNU XBoard is free software: you can redistribute it and/or modify
37  * it under the terms of the GNU General Public License as published by
38  * the Free Software Foundation, either version 3 of the License, or (at
39  * your option) any later version.
40  *
41  * GNU XBoard is distributed in the hope that it will be useful, but
42  * WITHOUT ANY WARRANTY; without even the implied warranty of
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44  * General Public License for more details.
45  *
46  * You should have received a copy of the GNU General Public License
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *
48  *
49  *------------------------------------------------------------------------
50  ** See the file ChangeLog for a revision history.  */
51 
52 #include "config.h"
53 
54 #include <stdio.h>
55 #include <math.h>
56 #include <cairo/cairo.h>
57 #include <cairo/cairo-xlib.h>
58 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
60 #include <pango/pangocairo.h>
61 
62 #if STDC_HEADERS
63 # include <stdlib.h>
64 # include <string.h>
65 #else /* not STDC_HEADERS */
66 extern char *getenv();
67 # if HAVE_STRING_H
68 #  include <string.h>
69 # else /* not HAVE_STRING_H */
70 #  include <strings.h>
71 # endif /* not HAVE_STRING_H */
72 #endif /* not STDC_HEADERS */
73 
74 #if ENABLE_NLS
75 #include <locale.h>
76 #endif
77 
78 #include "common.h"
79 
80 #include "backend.h"
81 #include "board.h"
82 #include "menus.h"
83 #include "dialogs.h"
84 #include "evalgraph.h"
85 #include "gettext.h"
86 #include "draw.h"
87 
88 
89 #ifdef __EMX__
90 #ifndef HAVE_USLEEP
91 #define HAVE_USLEEP
92 #endif
93 #define usleep(t)   _sleep2(((t)+500)/1000)
94 #endif
95 
96 #ifdef ENABLE_NLS
97 # define  _(s) gettext (s)
98 # define N_(s) gettext_noop (s)
99 #else
100 # define  _(s) (s)
101 # define N_(s)  s
102 #endif
103 
104 #define SOLID 0
105 #define OUTLINE 1
106 Boolean cairoAnimate;
107 Option *currBoard;
108 cairo_surface_t *csBoardWindow;
109 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
110 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
111 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
112 static RsvgHandle *svgPieces[2][(int)BlackPawn+4]; // vector pieces in store
113 static cairo_surface_t *pngBoardBitmap[2], *pngOriginalBoardBitmap[2];
114 int useTexture, textureW[2], textureH[2];
115 
116 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
117 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
118 
119 #define White(piece) ((int)(piece) < (int)BlackPawn)
120 
121 char svgDir[MSG_SIZ] = SVGDIR;
122 
123 char *crWhite = "#FFFFB0";
124 char *crBlack = "#AD5D3D";
125 
126 struct {
127   int x1, x2, y1, y2;
128 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
129 
130 void
SwitchWindow(int main)131 SwitchWindow (int main)
132 {
133     currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
134     csBoardWindow = DRAWABLE(currBoard);
135 }
136 
137 void
SelectPieces(VariantClass v)138 SelectPieces(VariantClass v)
139 {
140     int i;
141     for(i=0; i<2; i++) {
142 	int p;
143 	for(p=0; p<=(int)WhiteKing; p++)
144 	   pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
145 	if(v == VariantShogi && BOARD_HEIGHT != 7) { // no exceptions in Tori Shogi
146 	   pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteTokin];
147 	   pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
148 	   pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+3];
149 	   pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+4];
150 	   pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
151 	   pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteMonarch]; // for Sho Shogi
152 	}
153 #ifdef GOTHIC
154 	if(v == VariantGothic) {
155 	   pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
156 	}
157 #endif
158 	if(v == VariantSChess) {
159 	   pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
160 	   pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
161 	}
162 	if(v == VariantChuChess) {
163 	   pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
164 	}
165 	if(v == VariantChu) {
166 	   pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteClaw];
167 	   pngPieceBitmaps[i][(int)WhiteClaw]    = pngPieceBitmaps2[i][(int)WhiteNightrider];
168 	   pngPieceBitmaps[i][(int)WhiteUnicorn] = pngPieceBitmaps2[i][(int)WhiteHorned];
169 	   pngPieceBitmaps[i][(int)WhiteSilver]  = pngPieceBitmaps2[i][(int)WhiteStag];
170 	   pngPieceBitmaps[i][(int)WhiteFalcon]  = pngPieceBitmaps2[i][(int)WhiteEagle];
171 	   pngPieceBitmaps[i][(int)WhiteHorned]  = pngPieceBitmaps2[i][(int)WhiteUnicorn];
172 	   pngPieceBitmaps[i][(int)WhiteStag]    = pngPieceBitmaps2[i][(int)WhiteSilver];
173 	   pngPieceBitmaps[i][(int)WhiteEagle]   = pngPieceBitmaps2[i][(int)WhiteFalcon];
174 	}
175     }
176 }
177 
178 #define BoardSize int
179 void
InitDrawingSizes(BoardSize boardSize,int flags)180 InitDrawingSizes (BoardSize boardSize, int flags)
181 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
182     int boardWidth, boardHeight;
183     static int oldWidth, oldHeight;
184     static VariantClass oldVariant;
185     static int oldTwoBoards = 0, oldNrOfFiles = 0;
186 
187     if(!mainOptions[W_BOARD].handle) return;
188 
189     if(boardSize == -2 && gameInfo.variant != oldVariant
190                        && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
191 	squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
192 	if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
193         squareSize -= lineGap;
194 	CreatePNGPieces();
195         CreateGrid();
196     }
197     oldNrOfFiles = BOARD_WIDTH;
198 
199     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
200     oldTwoBoards = twoBoards;
201 
202     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
203     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
204     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
205 
206   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
207 
208     oldWidth = boardWidth; oldHeight = boardHeight;
209     CreateGrid();
210     CreateAnyPieces(0); // redo texture scaling
211 
212     /*
213      * Inhibit shell resizing.
214      */
215     ResizeBoardWindow(boardWidth, boardHeight, 0);
216 
217     DelayedDrag();
218   }
219 
220     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
221     // (only for xpm)
222 
223   if(gameInfo.variant != oldVariant) { // and only if variant changed
224 
225     SelectPieces(gameInfo.variant);
226 
227     oldVariant = gameInfo.variant;
228   }
229   CreateAnimVars();
230 }
231 
232 void
ExposeRedraw(Option * graph,int x,int y,int w,int h)233 ExposeRedraw (Option *graph, int x, int y, int w, int h)
234 {   // copy a selected part of the buffer bitmap to the display
235     cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
236     cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
237     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
238     cairo_rectangle(cr, x, y, w, h);
239     cairo_fill(cr);
240     cairo_destroy(cr);
241 }
242 
243 static void
CreatePNGBoard(char * s,int kind)244 CreatePNGBoard (char *s, int kind)
245 {
246     float w, h;
247     static float n[2] = { 1., 1. };
248     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
249     textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
250     if(strstr(s, ".png")) {
251 	cairo_surface_t *img = cairo_image_surface_create_from_png (s);
252 	if(img) {
253 	    char c, *p = s, *q;
254 	    int r, f;
255 	    if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
256 	    if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
257 	    useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
258 	    w = textureW[kind] = cairo_image_surface_get_width (img);
259 	    h = textureH[kind] = cairo_image_surface_get_height (img);
260 	    n[kind] = 1.;
261 	    while((q = strchr(p+1, '-'))) p = q; // find last '-'
262 	    if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
263 		if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
264 		textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
265 		textureH[kind] = (h*BOARD_HEIGHT)/r;
266 		n[kind] = r*squareSize/h; // scale to make it fit exactly vertically
267 	    } else
268 	    if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
269 		while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
270 	    } else {
271 		while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
272 	    }
273 	    if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
274 		// create scaled-up copy of the raw png image when it was too small
275 		cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
276 		cairo_t *cr = cairo_create(cs);
277 		pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
278 //		cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
279 		cairo_scale(cr, n[kind], n[kind]);
280 		cairo_set_source_surface (cr, img, 0, 0);
281 		cairo_paint (cr);
282 		cairo_destroy (cr);
283 	    }
284 	}
285     }
286 }
287 
288 char *pngPieceNames[] = // must be in same order as internal piece encoding
289 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
290   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
291   "Tile", "Tile", "Tile", "Tile", "Tile",
292   "GoldPawn", "Claw", "PromoHorse", "PromoDragon", "GoldLance", "PromoSword", "Prince", "Phoenix", "Kylin", "PromoRook", "PromoHSword",
293   "Dolphin", "Sword", "Leopard", "HSword", "GoldSilver", "Princess", "HCrown", "Knight", "Elephant", "PromoBishop",
294   "Tile", "Tile", "Tile", "Tile", "Tile", "King",
295   "Claw", "GoldKnight", "GoldLance", "GoldSilver", NULL
296 };
297 
298 char *backupPiece[] = { "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
299                         NULL, NULL, NULL, NULL, NULL,
300 			NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion" }; // pieces that map on other when not kanji
301 
302 RsvgHandle *
LoadSVG(char * dir,int color,int piece,int retry)303 LoadSVG (char *dir, int color, int piece, int retry)
304 {
305     char buf[MSG_SIZ];
306   RsvgHandle *svg=svgPieces[color][piece];
307   RsvgDimensionData svg_dimensions;
308   GError *svgerror=NULL;
309   cairo_surface_t *img;
310   cairo_t *cr;
311   char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
312 
313     if(!name) return NULL;
314 
315     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
316 
317     if(!svg && *dir) {
318       svg = rsvg_handle_new_from_file(buf, &svgerror);
319       if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
320 	snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
321 	svg = rsvg_handle_new_from_file(buf, &svgerror);
322       }
323     }
324 
325     if(svg) {
326       rsvg_handle_get_dimensions(svg, &svg_dimensions);
327       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
328 
329       cr = cairo_create(img);
330       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
331       rsvg_handle_render_cairo(svg, cr);
332       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
333         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
334         pngPieceImages[color][piece] = img;
335       }
336       cairo_destroy(cr);
337 
338       return svg;
339     }
340     if(!retry && piece >= WhiteGrasshopper && piece <= WhiteNothing) // pieces that are only different in kanji sets
341         return LoadSVG(dir, color, piece, 1);
342     if(svgerror)
343 	g_error_free(svgerror);
344     return NULL;
345 }
346 
347 static void
ScaleOnePiece(int color,int piece)348 ScaleOnePiece (int color, int piece)
349 {
350   float w, h;
351   char buf[MSG_SIZ];
352   cairo_surface_t *img, *cs;
353   cairo_t *cr;
354 
355   g_type_init ();
356 
357   svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
358 
359   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
360     if(*appData.pieceDirectory) { // user specified piece directory
361       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
362       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
363       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
364 	svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
365       } else pngPieceImages[color][piece] = img;
366     }
367   }
368 
369   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
370     static int warned = 0;
371     if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
372        && !warned && strcmp(pngPieceNames[piece], "Tile")) {         // but do not complain about missing 'Tile'
373       char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
374       printf("%s\n", msg); // give up
375       DisplayError(msg, 0);
376       warned = 1; // prevent error message being repeated for each piece type
377     }
378   }
379 
380   img = pngPieceImages[color][piece];
381 
382   // create new bitmap to hold scaled piece image (and remove any old)
383   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
384   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
385 
386   if(!img) return;
387 
388   // scaled copying of the raw png image
389   cr = cairo_create(cs);
390   w = cairo_image_surface_get_width (img);
391   h = cairo_image_surface_get_height (img);
392   cairo_scale(cr, squareSize/w, squareSize/h);
393   cairo_set_source_surface (cr, img, 0, 0);
394   cairo_paint (cr);
395   cairo_destroy (cr);
396 
397   if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
398     int stride = cairo_image_surface_get_stride(cs)/4;
399     int *buf = (int *) cairo_image_surface_get_data(cs);
400     int i, j, p;
401     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
402     cairo_surface_flush(cs);
403     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
404 	int r, a;
405 	float f;
406 	unsigned int c = buf[i*stride + j];
407 	a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
408         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
409 	buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
410 	buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
411 	if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
412 	if(appData.monoMode) {
413 	    if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
414 	    else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
415             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
416 	}
417     }
418     cairo_surface_mark_dirty(cs);
419   }
420 }
421 
422 void
CreatePNGPieces()423 CreatePNGPieces ()
424 {
425   int p;
426 
427   for(p=0; pngPieceNames[p]; p++) {
428     ScaleOnePiece(0, p);
429     ScaleOnePiece(1, p);
430   }
431   SelectPieces(gameInfo.variant);
432 }
433 
434 void
CreateAnyPieces(int p)435 CreateAnyPieces (int p)
436 {   // [HGM] taken out of main
437     if(p) CreatePNGPieces();
438     CreatePNGBoard(appData.liteBackTextureFile, 1);
439     CreatePNGBoard(appData.darkBackTextureFile, 0);
440 }
441 
442 void
InitDrawingParams(int reloadPieces)443 InitDrawingParams (int reloadPieces)
444 {
445     int i, p;
446     if(reloadPieces)
447     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
448 	if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
449 	pngPieceImages[i][p] = NULL;
450 	if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
451 	svgPieces[i][p] = NULL;
452     }
453     CreateAnyPieces(1);
454 }
455 
456 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
457 
458 float
Color(char * col,int n)459 Color (char *col, int n)
460 {
461   int c;
462   sscanf(col, "#%x", &c);
463   c = c >> 4*n & 255;
464   return c/255.;
465 }
466 
467 void
SetPen(cairo_t * cr,float w,char * col,int dash)468 SetPen (cairo_t *cr, float w, char *col, int dash)
469 {
470   static const double dotted[] = {4.0, 4.0};
471   static int len  = sizeof(dotted) / sizeof(dotted[0]);
472   cairo_set_line_width (cr, w);
473   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
474   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
475 }
476 
DrawSeekAxis(int x,int y,int xTo,int yTo)477 void DrawSeekAxis( int x, int y, int xTo, int yTo )
478 {
479     cairo_t *cr;
480 
481     /* get a cairo_t */
482     cr = cairo_create (csBoardWindow);
483 
484     cairo_move_to (cr, x, y);
485     cairo_line_to(cr, xTo, yTo );
486 
487     SetPen(cr, 2, "#000000", 0);
488     cairo_stroke(cr);
489 
490     /* free memory */
491     cairo_destroy (cr);
492     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
493 }
494 
DrawSeekBackground(int left,int top,int right,int bottom)495 void DrawSeekBackground( int left, int top, int right, int bottom )
496 {
497     cairo_t *cr = cairo_create (csBoardWindow);
498 
499     cairo_rectangle (cr, left, top, right-left, bottom-top);
500 
501     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
502     cairo_fill(cr);
503 
504     /* free memory */
505     cairo_destroy (cr);
506     GraphExpose(currBoard, left, top, right-left, bottom-top);
507 }
508 
DrawSeekText(char * buf,int x,int y)509 void DrawSeekText(char *buf, int x, int y)
510 {
511     cairo_t *cr = cairo_create (csBoardWindow);
512 
513     cairo_select_font_face (cr, "Sans",
514 			    CAIRO_FONT_SLANT_NORMAL,
515 			    CAIRO_FONT_WEIGHT_NORMAL);
516 
517     cairo_set_font_size (cr, 12.0);
518 
519     cairo_move_to (cr, x, y+4);
520     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
521     cairo_show_text( cr, buf);
522 
523     /* free memory */
524     cairo_destroy (cr);
525     GraphExpose(currBoard, x-5, y-10, 60, 15);
526 }
527 
DrawSeekDot(int x,int y,int colorNr)528 void DrawSeekDot(int x, int y, int colorNr)
529 {
530     cairo_t *cr = cairo_create (csBoardWindow);
531     int square = colorNr & 0x80;
532     colorNr &= 0x7F;
533 
534     if(square)
535 	cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
536     else
537 	cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
538 
539     SetPen(cr, 2, "#000000", 0);
540     cairo_stroke_preserve(cr);
541     switch (colorNr) {
542       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0);	break;
543       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
544       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
545     }
546     cairo_fill(cr);
547 
548     /* free memory */
549     cairo_destroy (cr);
550     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
551 }
552 
553 void
InitDrawingHandle(Option * opt)554 InitDrawingHandle (Option *opt)
555 {
556     csBoardWindow = DRAWABLE(opt);
557 }
558 
559 void
CreateGrid()560 CreateGrid ()
561 {
562     int i, j;
563 
564     if (lineGap == 0) return;
565 
566     /* [HR] Split this into 2 loops for non-square boards. */
567 
568     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
569         gridSegments[i].x1 = 0;
570         gridSegments[i].x2 =
571           lineGap + BOARD_WIDTH * (squareSize + lineGap);
572         gridSegments[i].y1 = gridSegments[i].y2
573           = lineGap / 2 + (i * (squareSize + lineGap));
574     }
575 
576     for (j = 0; j < BOARD_WIDTH + 1; j++) {
577         gridSegments[j + i].y1 = 0;
578         gridSegments[j + i].y2 =
579           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
580         gridSegments[j + i].x1 = gridSegments[j + i].x2
581           = lineGap / 2 + (j * (squareSize + lineGap));
582     }
583 }
584 
585 void
DrawGrid()586 DrawGrid()
587 {
588   /* draws a grid starting around Nx, Ny squares starting at x,y */
589   int i;
590   float odd = (lineGap & 1)/2.;
591   cairo_t *cr;
592 
593   /* get a cairo_t */
594   cr = cairo_create (csBoardWindow);
595 
596   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
597   SetPen(cr, lineGap, "#000000", 0);
598 
599   /* lines in X */
600   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
601     {
602       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
603       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
604       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
605       cairo_stroke (cr);
606     }
607 
608   /* free memory */
609   cairo_destroy (cr);
610 
611   return;
612 }
613 
614 void
DrawBorder(int x,int y,int type,int odd)615 DrawBorder (int x, int y, int type, int odd)
616 {
617     cairo_t *cr;
618     char *col;
619 
620     switch(type) {
621 	case 0: col = "#000000"; break;
622 	case 1: col = appData.highlightSquareColor; break;
623 	case 2: col = appData.premoveHighlightColor; break;
624 	default: col = "#808080"; break; // cannot happen
625     }
626     cr = cairo_create(csBoardWindow);
627     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
628     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
629     SetPen(cr, lineGap, col, 0);
630     cairo_stroke(cr);
631     cairo_destroy(cr);
632     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
633 }
634 
635 static int
CutOutSquare(int x,int y,int * x0,int * y0,int kind)636 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
637 {
638     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
639     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
640     *x0 = 0; *y0 = 0;
641     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
642     if(textureW[kind] < W*squareSize)
643 	*x0 = (textureW[kind] - squareSize) * nx/(W-1);
644     else
645 	*x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
646     if(textureH[kind] < H*squareSize)
647 	*y0 = (textureH[kind] - squareSize) * ny/(H-1);
648     else
649 	*y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
650     return 1;
651 }
652 
653 void
DrawLogo(Option * opt,void * logo)654 DrawLogo (Option *opt, void *logo)
655 {
656     cairo_surface_t *img;
657     cairo_t *cr;
658     int w, h;
659 
660     if(!logo || !opt) return;
661     img = cairo_image_surface_create_from_png (logo);
662     w = cairo_image_surface_get_width (img);
663     h = cairo_image_surface_get_height (img);
664     cr = cairo_create(DRAWABLE(opt));
665 //    cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
666     cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
667     cairo_set_source_surface (cr, img, 0, 0);
668     cairo_paint (cr);
669     cairo_destroy (cr);
670     cairo_surface_destroy (img);
671     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
672 }
673 
674 static void
BlankSquare(cairo_surface_t * dest,int x,int y,int color,ChessSquare piece,int fac)675 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
676 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
677     int x0, y0;
678     cairo_t *cr;
679 
680     cr = cairo_create (dest);
681 
682     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
683 	    cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
684 	    cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
685 	    cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
686 	    cairo_fill (cr);
687 	    cairo_destroy (cr);
688     } else { // evenly colored squares
689 	char *col = NULL;
690 	switch (color) {
691 	  case 0: col = appData.darkSquareColor; break;
692 	  case 1: col = appData.lightSquareColor; break;
693 	  case 2: col = "#000000"; break;
694 	  default: col = "#808080"; break; // cannot happen
695 	}
696 	SetPen(cr, 2.0, col, 0);
697 	cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
698 	cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
699 	cairo_fill (cr);
700 	cairo_destroy (cr);
701     }
702 }
703 
704 static void
pngDrawPiece(cairo_surface_t * dest,ChessSquare piece,int square_color,int x,int y)705 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
706 {
707     int kind;
708     cairo_t *cr;
709 
710     if ((int)piece < (int) BlackPawn) {
711 	kind = 0;
712     } else {
713 	kind = 1;
714 	piece -= BlackPawn;
715     }
716     if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
717     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
718     cr = cairo_create (dest);
719     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
720     cairo_paint(cr);
721     cairo_destroy (cr);
722 }
723 
724 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
725 
726 void
DoDrawDot(cairo_surface_t * cs,int marker,int x,int y,int r)727 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
728 {
729 	cairo_t *cr;
730 
731 	cr = cairo_create(cs);
732 	cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
733 	if(appData.monoMode) {
734 	    SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
735 	    cairo_stroke_preserve(cr);
736 	    SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
737 	} else {
738 	    SetPen(cr, 2, markerColor[marker-1], 0);
739 	}
740 	cairo_fill(cr);
741 
742 	cairo_destroy(cr);
743 }
744 
745 void
DrawDot(int marker,int x,int y,int r)746 DrawDot (int marker, int x, int y, int r)
747 { // used for atomic captures; no need to draw on backup
748   DoDrawDot(csBoardWindow, marker, x, y, r);
749   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
750 }
751 
752 static void
DrawUnicode(cairo_surface_t * canvas,char * string,int x,int y,char id,int flip)753 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
754 {
755 //	cairo_text_extents_t te;
756 	cairo_t *cr;
757 	int s = 1 - 2*flip;
758 	PangoLayout *layout;
759 	PangoFontDescription *desc;
760 	PangoRectangle r;
761 	char fontName[MSG_SIZ];
762 
763 	cr = cairo_create (canvas);
764 	layout = pango_cairo_create_layout(cr);
765 	pango_layout_set_text(layout, string, -1);
766 	snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
767 	desc = pango_font_description_from_string(fontName);
768 	pango_layout_set_font_description(layout, desc);
769 	pango_font_description_free(desc);
770         pango_layout_get_pixel_extents(layout, NULL, &r);
771 	cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
772 	if(s < 0) cairo_rotate(cr, G_PI);
773 	cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
774 	pango_cairo_update_layout(cr, layout);
775 	pango_cairo_show_layout(cr, layout);
776 	g_object_unref(layout);
777 	cairo_destroy(cr);
778 }
779 
780 static void
DrawText(char * string,int x,int y,int align)781 DrawText (char *string, int x, int y, int align)
782 {
783 	int xx = x, yy = y;
784 	cairo_text_extents_t te;
785 	cairo_t *cr;
786 
787 	cr = cairo_create (csBoardWindow);
788 	cairo_select_font_face (cr, "Sans",
789 		    CAIRO_FONT_SLANT_NORMAL,
790 		    CAIRO_FONT_WEIGHT_BOLD);
791 
792 	cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
793 	// calculate where it goes
794 	cairo_text_extents (cr, string, &te);
795 
796 	if (align == 1) {
797 	    xx += squareSize - te.width - te.x_bearing - 1;
798 	    yy += squareSize - te.height - te.y_bearing - 1;
799 	} else if (align == 2) {
800 	    xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
801 	} else if (align == 3) {
802 	    xx += squareSize - te.width -te.x_bearing - 1;
803 	    yy += -te.y_bearing + 3;
804 	} else if (align == 4) {
805 	    xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
806 	}
807 
808 	cairo_move_to (cr, xx-1, yy);
809 	if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
810 	else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
811 	cairo_show_text (cr, string);
812 	cairo_destroy (cr);
813 }
814 
815 void
InscribeKanji(cairo_surface_t * canvas,ChessSquare piece,int x,int y)816 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
817 {
818     char *p, *q, buf[10];
819     int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
820     if(piece == EmptySquare) return;
821     if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
822     p = appData.inscriptions;
823     n = piece;
824     while(piece > WhitePawn) {
825       if(*p++ == NULLCHAR) {
826         if(n != WhiteKing) return;
827         p = q;
828         break;
829       }
830       q = p - 1;
831       while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
832       piece--;
833     }
834     strncpy(buf, p, 10);
835     for(q=buf; (*++q & 0xC0) == 0x80;);
836     *q = NULLCHAR;
837     DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
838 }
839 
840 void
DrawOneSquare(int x,int y,ChessSquare piece,int square_color,int marker,char * tString,char * bString,int align)841 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
842 {   // basic front-end board-draw function: takes care of everything that can be in square:
843     // piece, background, coordinate/count, marker dot
844 
845     if (piece == EmptySquare) {
846 	BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
847     } else {
848 	pngDrawPiece(csBoardWindow, piece, square_color, x, y);
849         if(appData.inscriptions[0]) InscribeKanji(csBoardWindow, piece, x, y);
850     }
851 
852     if(align) { // square carries inscription (coord or piece count)
853 	if(align > 1) DrawText(tString, x, y, align);       // top (rank or count)
854 	if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
855     }
856 
857     if(marker) { // print fat marker dot, if requested
858 	DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
859     }
860 }
861 
862 /****	Animation code by Hugh Fisher, DCS, ANU. ****/
863 
864 /*	Masks for XPM pieces. Black and white pieces can have
865 	different shapes, but in the interest of retaining my
866 	sanity pieces must have the same outline on both light
867 	and dark squares, and all pieces must use the same
868 	background square colors/images.		*/
869 
870 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
871 
872 static void
InitAnimState(AnimNr anr)873 InitAnimState (AnimNr anr)
874 {
875     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
876     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
877     c_animBufs[anr+4] = csBoardWindow;
878     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
879     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
880 }
881 
882 void
CreateAnimVars()883 CreateAnimVars ()
884 {
885   InitAnimState(Game);
886   InitAnimState(Player);
887 }
888 
889 static void
CairoOverlayPiece(ChessSquare piece,cairo_surface_t * dest)890 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
891 {
892   static cairo_t *pieceSource;
893   pieceSource = cairo_create (dest);
894   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
895   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
896   else cairo_paint(pieceSource);
897   cairo_destroy (pieceSource);
898   if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
899 }
900 
901 void
InsertPiece(AnimNr anr,ChessSquare piece)902 InsertPiece (AnimNr anr, ChessSquare piece)
903 {
904     CairoOverlayPiece(piece, c_animBufs[anr]);
905 }
906 
907 void
DrawBlank(AnimNr anr,int x,int y,int startColor)908 DrawBlank (AnimNr anr, int x, int y, int startColor)
909 {
910     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
911 }
912 
CopyRectangle(AnimNr anr,int srcBuf,int destBuf,int srcX,int srcY,int width,int height,int destX,int destY)913 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
914 		 int srcX, int srcY, int width, int height, int destX, int destY)
915 {
916 	cairo_t *cr;
917 	c_animBufs[anr+4] = csBoardWindow;
918 	cr = cairo_create (c_animBufs[anr+destBuf]);
919 	cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
920 	cairo_rectangle (cr, destX, destY, width, height);
921 	cairo_fill (cr);
922 	cairo_destroy (cr);
923 	if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
924 	    GraphExpose(currBoard, destX, destY, width, height);
925 }
926 
927 void
SetDragPiece(AnimNr anr,ChessSquare piece)928 SetDragPiece (AnimNr anr, ChessSquare piece)
929 {
930 }
931 
932 /* [AS] Arrow highlighting support */
933 
934 void
DoDrawPolygon(cairo_surface_t * cs,Pnt arrow[],int nr)935 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
936 {
937     cairo_t *cr;
938     int i;
939     cr = cairo_create (cs);
940     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
941     for (i=0;i<nr;i++) {
942         cairo_line_to(cr, arrow[i].x, arrow[i].y);
943     }
944     if(appData.monoMode) { // should we always outline arrow?
945         cairo_line_to(cr, arrow[0].x, arrow[0].y);
946         SetPen(cr, 2, "#000000", 0);
947         cairo_stroke_preserve(cr);
948     }
949     SetPen(cr, 2, appData.highlightSquareColor, 0);
950     cairo_fill(cr);
951 
952     /* free memory */
953     cairo_destroy (cr);
954 }
955 
956 void
DrawPolygon(Pnt arrow[],int nr)957 DrawPolygon (Pnt arrow[], int nr)
958 {
959     DoDrawPolygon(csBoardWindow, arrow, nr);
960 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
961 }
962 
963 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
964 
965 static void
ChoosePen(cairo_t * cr,int i)966 ChoosePen(cairo_t *cr, int i)
967 {
968   switch(i) {
969     case PEN_BLACK:
970       SetPen(cr, 1.0, "#000000", 0);
971       break;
972     case PEN_DOTTED:
973       SetPen(cr, 1.0, "#A0A0A0", 1);
974       break;
975     case PEN_BLUEDOTTED:
976       SetPen(cr, 1.0, "#0000FF", 1);
977       break;
978     case PEN_BOLDWHITE:
979       SetPen(cr, 3.0, crWhite, 0);
980       break;
981     case PEN_BOLDBLACK:
982       SetPen(cr, 3.0, crBlack, 0);
983       break;
984     case PEN_BACKGD:
985       SetPen(cr, 3.0, "#E0E0F0", 0);
986       break;
987   }
988 }
989 
990 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
991 void
DrawSegment(int x,int y,int * lastX,int * lastY,int penType)992 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
993 {
994   static int curX, curY;
995 
996   if(penType != PEN_NONE) {
997     cairo_t *cr = cairo_create(DRAWABLE(disp));
998     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
999     cairo_move_to (cr, curX, curY);
1000     cairo_line_to (cr, x,y);
1001     ChoosePen(cr, penType);
1002     cairo_stroke (cr);
1003     cairo_destroy (cr);
1004   }
1005 
1006   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1007   curX = x; curY = y;
1008 }
1009 
1010 // front-end wrapper for drawing functions to do rectangles
1011 void
DrawRectangle(int left,int top,int right,int bottom,int side,int style)1012 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1013 {
1014   cairo_t *cr;
1015 
1016   cr = cairo_create (DRAWABLE(disp));
1017   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1018   cairo_rectangle (cr, left, top, right-left, bottom-top);
1019   switch(side)
1020     {
1021     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1022     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1023     case 2: ChoosePen(cr, PEN_BACKGD); break;
1024     }
1025   cairo_fill (cr);
1026 
1027   if(style != FILLED)
1028     {
1029       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1030       ChoosePen(cr, PEN_BLACK);
1031       cairo_stroke (cr);
1032     }
1033 
1034   cairo_destroy(cr);
1035 }
1036 
1037 // front-end wrapper for putting text in graph
1038 void
DrawEvalText(char * buf,int cbBuf,int y)1039 DrawEvalText (char *buf, int cbBuf, int y)
1040 {
1041     // the magic constants 8 and 5 should really be derived from the font size somehow
1042   cairo_text_extents_t extents;
1043   cairo_t *cr = cairo_create(DRAWABLE(disp));
1044 
1045   /* GTK-TODO this has to go into the font-selection */
1046   cairo_select_font_face (cr, "Sans",
1047 			  CAIRO_FONT_SLANT_NORMAL,
1048 			  CAIRO_FONT_WEIGHT_NORMAL);
1049   cairo_set_font_size (cr, 12.0);
1050 
1051 
1052   cairo_text_extents (cr, buf, &extents);
1053 
1054   cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1055   cairo_text_path (cr, buf);
1056   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1057   cairo_fill_preserve (cr);
1058   cairo_set_source_rgb (cr, 0, 1.0, 0);
1059   cairo_set_line_width (cr, 0.1);
1060   cairo_stroke (cr);
1061 
1062   /* free memory */
1063   cairo_destroy (cr);
1064 }
1065