Add DATADIR as shortcut folder to file chooser
[xboard.git] / draw.c
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, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * The following terms apply to Digital Equipment Corporation's copyright
12  * interest in XBoard:
13  * ------------------------------------------------------------------------
14  * All Rights Reserved
15  *
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.
23  *
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
30  * SOFTWARE.
31  * ------------------------------------------------------------------------
32  *
33  * The following terms apply to the enhanced version of XBoard
34  * distributed by the Free Software Foundation:
35  * ------------------------------------------------------------------------
36  *
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.
41  *
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.
46  *
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/.  *
49  *
50  *------------------------------------------------------------------------
51  ** See the file ChangeLog for a revision history.  */
52
53 #include "config.h"
54
55 #include <stdio.h>
56 #include <math.h>
57 #include <cairo/cairo.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];   // 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];
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
131 SwitchWindow (int main)
132 {
133     currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
134 //    CsBoardWindow = DRAWABLE(currBoard);
135 }
136
137
138 static void
139 NewCanvas (Option *graph)
140 {
141         cairo_t *cr;
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);
149         cairo_fill(cr);
150         cairo_destroy (cr);
151         graph->min &= ~REPLACE;
152 }
153
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);
159 }
160
161
162 void
163 SelectPieces(VariantClass v)
164 {
165     int i;
166     for(i=0; i<2; i++) {
167         int p;
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
177         }
178 #ifdef GOTHIC
179         if(v == VariantGothic) {
180            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
181         }
182 #endif
183         if(v == VariantSChess) {
184            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
185            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
186         }
187         if(v == VariantChuChess) {
188            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
189         }
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];
203         }
204     }
205 }
206
207 #define BoardSize int
208 void
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;
215
216     if(!mainOptions[W_BOARD].handle) return;
217
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();
224         CreateGrid();
225     }
226     oldNrOfFiles = BOARD_WIDTH;
227
228     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
229     oldTwoBoards = twoBoards;
230
231     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
232     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
233     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
234
235   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
236
237     oldWidth = boardWidth; oldHeight = boardHeight;
238     CreateGrid();
239     CreateAnyPieces(0); // redo texture scaling
240
241     /*
242      * Inhibit shell resizing.
243      */
244     ResizeBoardWindow(boardWidth, boardHeight, 0);
245
246     DelayedDrag();
247   }
248
249     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
250     // (only for xpm)
251
252   if(gameInfo.variant != oldVariant) { // and only if variant changed
253
254     SelectPieces(gameInfo.variant);
255
256     oldVariant = gameInfo.variant;
257   }
258   CreateAnimVars();
259 }
260
261 void
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);
268     cairo_fill(cr);
269     cairo_destroy(cr);
270 }
271
272 static int modV[2], modH[2];
273
274 static void
275 CreatePNGBoard (char *s, int kind)
276 {
277     float w, h;
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) {
284             char c, *p = s, *q;
285             int r, f;
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;
299             } else
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]++;
302             } else {
303                 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
304             }
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);
313                 cairo_paint (cr);
314                 cairo_destroy (cr);
315             }
316         }
317     }
318 }
319
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",
327   NULL
328 };
329
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"
335 };
336
337 RsvgHandle *
338 LoadSVG (char *dir, int color, int piece, int retry)
339 {
340     char buf[MSG_SIZ];
341   RsvgHandle *svg=svgPieces[color][piece];
342   RsvgDimensionData svg_dimensions;
343   GError *svgerror=NULL;
344   cairo_surface_t *img;
345   cairo_t *cr;
346   char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
347
348     if(!name) return NULL;
349
350     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
351
352     if(!svg && *dir) {
353       svg = rsvg_handle_new_from_file(buf, &svgerror);
354       if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
355         snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
356         svg = rsvg_handle_new_from_file(buf, &svgerror);
357       }
358     }
359
360     if(svg) {
361       rsvg_handle_get_dimensions(svg, &svg_dimensions);
362       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
363
364       cr = cairo_create(img);
365       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
366       rsvg_handle_render_cairo(svg, cr);
367       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
368         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
369         pngPieceImages[color][piece] = img;
370       }
371       cairo_destroy(cr);
372
373       return svg;
374     }
375     if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
376         return LoadSVG(dir, color, piece, 1);
377     if(svgerror)
378         g_error_free(svgerror);
379     return NULL;
380 }
381
382 static void
383 ScaleOnePiece (int color, int piece)
384 {
385   float w, h;
386   char buf[MSG_SIZ];
387   cairo_surface_t *img, *cs;
388   cairo_t *cr;
389
390   g_type_init ();
391
392   svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
393
394   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
395     if(*appData.pieceDirectory) { // user specified piece directory
396       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
397       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
398       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
399         svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
400       } else pngPieceImages[color][piece] = img;
401     }
402   }
403
404   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
405     static int warned = 0;
406     if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg 
407        && !warned && strcmp(pngPieceNames[piece], "Tile")) {         // but do not complain about missing 'Tile'
408       char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
409       printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
410       DisplayError(msg, 0);
411       warned = 1; // prevent error message being repeated for each piece type
412     }
413   }
414
415   img = pngPieceImages[color][piece];
416
417   // create new bitmap to hold scaled piece image (and remove any old)
418   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
419   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
420
421   if(!img) return;
422
423   // scaled copying of the raw png image
424   cr = cairo_create(cs);
425   w = cairo_image_surface_get_width (img);
426   h = cairo_image_surface_get_height (img);
427   cairo_scale(cr, squareSize/w, squareSize/h);
428   cairo_set_source_surface (cr, img, 0, 0);
429   cairo_paint (cr);
430   cairo_destroy (cr);
431
432   if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
433     int stride = cairo_image_surface_get_stride(cs)/4;
434     int *buf = (int *) cairo_image_surface_get_data(cs);
435     int i, j, p;
436     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
437     cairo_surface_flush(cs);
438     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
439         int r, a;
440         float f;
441         unsigned int c = buf[i*stride + j];
442         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
443         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
444         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
445         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
446         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
447         if(appData.monoMode) {
448             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
449             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
450             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
451         }
452     }
453     cairo_surface_mark_dirty(cs);
454   }
455 }
456
457 void
458 CreatePNGPieces ()
459 {
460   int p;
461
462   for(p=0; pngPieceNames[p]; p++) {
463     ScaleOnePiece(0, p);
464     ScaleOnePiece(1, p);
465   }
466   SelectPieces(gameInfo.variant);
467 }
468
469 void
470 CreateAnyPieces (int p)
471 {   // [HGM] taken out of main
472     if(p) CreatePNGPieces();
473     CreatePNGBoard(appData.liteBackTextureFile, 1);
474     CreatePNGBoard(appData.darkBackTextureFile, 0);
475 }
476
477 void
478 InitDrawingParams (int reloadPieces)
479 {
480     int i, p;
481     if(reloadPieces)
482     for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
483         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
484         pngPieceImages[i][p] = NULL;
485         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
486         svgPieces[i][p] = NULL;
487     }
488     CreateAnyPieces(1);
489 }
490
491 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
492
493 float
494 Color (char *col, int n)
495 {
496   int c;
497   sscanf(col, "#%x", &c);
498   c = c >> 4*n & 255;
499   return c/255.;
500 }
501
502 void
503 SetPen (cairo_t *cr, float w, char *col, int dash)
504 {
505   static const double dotted[] = {4.0, 4.0};
506   static int len  = sizeof(dotted) / sizeof(dotted[0]);
507   cairo_set_line_width (cr, w);
508   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
509   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
510 }
511
512 void DrawSeekAxis( int x, int y, int xTo, int yTo )
513 {
514     cairo_t *cr;
515
516     /* get a cairo_t */
517     cr = cairo_create (CsBoardWindow(currBoard));
518
519     cairo_move_to (cr, x, y);
520     cairo_line_to(cr, xTo, yTo );
521
522     SetPen(cr, 2, "#000000", 0);
523     cairo_stroke(cr);
524
525     /* free memory */
526     cairo_destroy (cr);
527     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
528 }
529
530 void DrawSeekBackground( int left, int top, int right, int bottom )
531 {
532     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
533
534     cairo_rectangle (cr, left, top, right-left, bottom-top);
535
536     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
537     cairo_fill(cr);
538
539     /* free memory */
540     cairo_destroy (cr);
541     GraphExpose(currBoard, left, top, right-left, bottom-top);
542 }
543
544 void DrawSeekText(char *buf, int x, int y)
545 {
546     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
547
548     cairo_select_font_face (cr, "Sans",
549                             CAIRO_FONT_SLANT_NORMAL,
550                             CAIRO_FONT_WEIGHT_NORMAL);
551
552     cairo_set_font_size (cr, 12.0);
553
554     cairo_move_to (cr, x, y+4);
555     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
556     cairo_show_text( cr, buf);
557
558     /* free memory */
559     cairo_destroy (cr);
560     GraphExpose(currBoard, x-5, y-10, 60, 15);
561 }
562
563 void DrawSeekDot(int x, int y, int colorNr)
564 {
565     cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
566     int square = colorNr & 0x80;
567     colorNr &= 0x7F;
568
569     if(square)
570         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
571     else
572         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
573
574     SetPen(cr, 2, "#000000", 0);
575     cairo_stroke_preserve(cr);
576     switch (colorNr) {
577       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
578       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
579       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
580     }
581     cairo_fill(cr);
582
583     /* free memory */
584     cairo_destroy (cr);
585     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
586 }
587
588 void
589 InitDrawingHandle (Option *opt)
590 {
591 //    CsBoardWindow = DRAWABLE(opt);
592     currBoard = opt;
593 }
594
595 void
596 CreateGrid ()
597 {
598     int i, j;
599
600     if (lineGap == 0) return;
601
602     /* [HR] Split this into 2 loops for non-square boards. */
603
604     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
605         gridSegments[i].x1 = 0;
606         gridSegments[i].x2 =
607           lineGap + BOARD_WIDTH * (squareSize + lineGap);
608         gridSegments[i].y1 = gridSegments[i].y2
609           = lineGap / 2 + (i * (squareSize + lineGap));
610     }
611
612     for (j = 0; j < BOARD_WIDTH + 1; j++) {
613         gridSegments[j + i].y1 = 0;
614         gridSegments[j + i].y2 =
615           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
616         gridSegments[j + i].x1 = gridSegments[j + i].x2
617           = lineGap / 2 + (j * (squareSize + lineGap));
618     }
619 }
620
621 void
622 DrawGrid()
623 {
624   /* draws a grid starting around Nx, Ny squares starting at x,y */
625   int i;
626   float odd = (lineGap & 1)/2.;
627   cairo_t *cr;
628
629   /* get a cairo_t */
630   cr = cairo_create (CsBoardWindow(currBoard));
631
632   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
633   SetPen(cr, lineGap, "#000000", 0);
634
635   /* lines in X */
636   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
637     {
638       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
639       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
640       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
641       cairo_stroke (cr);
642     }
643
644   /* free memory */
645   cairo_destroy (cr);
646
647   return;
648 }
649
650 void
651 DrawBorder (int x, int y, int type, int odd)
652 {
653     cairo_t *cr;
654     char *col;
655
656     switch(type) {
657         case 0: col = "#000000"; break;
658         case 1: col = appData.highlightSquareColor; break;
659         case 2: col = appData.premoveHighlightColor; break;
660         default: col = "#808080"; break; // cannot happen
661     }
662     cr = cairo_create(CsBoardWindow(currBoard));
663     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
664     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
665     SetPen(cr, lineGap, col, 0);
666     cairo_stroke(cr);
667     cairo_destroy(cr);
668 //    GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
669 }
670
671 static int
672 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
673 {
674     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
675     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
676     *x0 = 0; *y0 = 0;
677     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
678     if(modV[kind] > 0) nx %= modH[kind], ny %= modV[kind]; // tile fixed-format board periodically to extend it
679     if(textureW[kind] < W*squareSize)
680         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
681     else
682         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
683     if(textureH[kind] < H*squareSize)
684         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
685     else
686         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
687     return 1;
688 }
689
690 void
691 DrawLogo (Option *opt, void *logo)
692 {
693     cairo_surface_t *img;
694     cairo_t *cr;
695     int w, h;
696
697     if(!opt) return;
698     cr = cairo_create(CsBoardWindow(opt));
699     cairo_rectangle (cr, 0, 0, opt->max, opt->value);
700     cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
701     cairo_fill(cr); // paint background in case logo does not exist
702     if(logo) {
703         img = cairo_image_surface_create_from_png (logo);
704         if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
705             w = cairo_image_surface_get_width (img);
706             h = cairo_image_surface_get_height (img);
707 //        cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
708             cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
709             cairo_set_source_surface (cr, img, 0, 0);
710             cairo_paint (cr);
711         }
712         cairo_surface_destroy (img);
713     }
714     cairo_destroy (cr);
715     GraphExpose(opt, 0, 0, opt->max, opt->value);
716 }
717
718 static void
719 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
720 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
721     int x0, y0;
722     cairo_t *cr;
723
724     cr = cairo_create (dest);
725
726     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
727             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
728             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
729             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
730             cairo_fill (cr);
731             cairo_destroy (cr);
732     } else { // evenly colored squares
733         char *col = NULL;
734         switch (color) {
735           case 0: col = appData.darkSquareColor; break;
736           case 1: col = appData.lightSquareColor; break;
737           case 2: col = "#000000"; break;
738           default: col = "#808080"; break; // cannot happen
739         }
740         SetPen(cr, 2.0, col, 0);
741         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
742         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
743         cairo_fill (cr);
744         cairo_destroy (cr);
745     }
746 }
747
748 static void
749 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
750 {
751     int kind;
752     cairo_t *cr;
753
754     if ((int)piece < (int) BlackPawn) {
755         kind = 0;
756     } else {
757         kind = 1;
758         piece -= BlackPawn;
759     }
760     if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
761     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
762     cr = cairo_create (dest);
763     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
764     cairo_paint(cr);
765     cairo_destroy (cr);
766 }
767
768 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
769
770 void
771 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
772 {
773         cairo_t *cr;
774
775         cr = cairo_create(cs);
776         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
777         if(appData.monoMode) {
778             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
779             cairo_stroke_preserve(cr);
780             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
781         } else {
782             SetPen(cr, 2, markerColor[marker-1], 0);
783         }
784         cairo_fill(cr);
785
786         cairo_destroy(cr);
787 }
788
789 void
790 DrawDot (int marker, int x, int y, int r)
791 { // used for atomic captures; no need to draw on backup
792   DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
793   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
794 }
795
796 static void
797 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
798 {
799 //      cairo_text_extents_t te;
800         cairo_t *cr;
801         int s = 1 - 2*flip;
802         PangoLayout *layout;
803         PangoFontDescription *desc;
804         PangoRectangle r;
805         char fontName[MSG_SIZ];
806
807         cr = cairo_create (canvas);
808         layout = pango_cairo_create_layout(cr);
809         pango_layout_set_text(layout, string, -1);
810         snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
811         desc = pango_font_description_from_string(fontName);
812         pango_layout_set_font_description(layout, desc);
813         pango_font_description_free(desc);
814         pango_layout_get_pixel_extents(layout, NULL, &r);
815         cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
816         if(s < 0) cairo_rotate(cr, G_PI);
817         cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
818         pango_cairo_update_layout(cr, layout);
819         pango_cairo_show_layout(cr, layout);
820         g_object_unref(layout);
821         cairo_destroy(cr);
822 }
823
824 void
825 DrawText (char *string, int x, int y, int align)
826 {
827         int xx = x, yy = y;
828         cairo_text_extents_t te;
829         cairo_t *cr;
830
831         cr = cairo_create (CsBoardWindow(currBoard));
832         cairo_select_font_face (cr, "Sans",
833                     CAIRO_FONT_SLANT_NORMAL,
834                     CAIRO_FONT_WEIGHT_BOLD);
835
836         cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
837         // calculate where it goes
838         cairo_text_extents (cr, string, &te);
839
840         if (align == 1) {
841             xx += squareSize - te.width - te.x_bearing - 1;
842             yy += squareSize - te.height - te.y_bearing - 1;
843         } else if (align == 2) {
844             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
845         } else if (align == 3) {
846             xx += squareSize - te.width -te.x_bearing - 1;
847             yy += -te.y_bearing + 3;
848         } else if (align == 4) {
849             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
850         }
851
852         cairo_move_to (cr, xx-1, yy);
853         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
854         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
855         cairo_show_text (cr, string);
856         cairo_destroy (cr);
857 }
858
859 void
860 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
861 {
862     char *p, *q, buf[10];
863     int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
864     if(piece == EmptySquare) return;
865     if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
866     p = appData.inscriptions;
867     n = piece;
868     while(piece > WhitePawn) {
869       if(*p++ == NULLCHAR) {
870         if(n != WhiteKing) return;
871         p = q;
872         break;
873       }
874       q = p - 1;
875       while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
876       piece--;
877     }
878     strncpy(buf, p, 10);
879     for(q=buf; (*++q & 0xC0) == 0x80;);
880     *q = NULLCHAR;
881     DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
882 }
883
884 void
885 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
886 {   // basic front-end board-draw function: takes care of everything that can be in square:
887     // piece, background, coordinate/count, marker dot
888
889     if (piece == EmptySquare) {
890         BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
891     } else {
892         pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
893         if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
894     }
895
896     if(align) { // square carries inscription (coord or piece count)
897         if(align > 1) DrawText(tString, x, y, align);       // top (rank or count)
898         if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
899     }
900
901     if(marker) { // print fat marker dot, if requested
902         DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
903     }
904 }
905
906 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
907
908 /*      Masks for XPM pieces. Black and white pieces can have
909         different shapes, but in the interest of retaining my
910         sanity pieces must have the same outline on both light
911         and dark squares, and all pieces must use the same
912         background square colors/images.                */
913
914 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
915
916 static void
917 InitAnimState (AnimNr anr)
918 {
919     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
920     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
921     c_animBufs[anr+4] = CsBoardWindow(currBoard);
922     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
923     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
924 }
925
926 void
927 CreateAnimVars ()
928 {
929   InitAnimState(Game);
930   InitAnimState(Player);
931 }
932
933 static void
934 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
935 {
936   static cairo_t *pieceSource;
937   pieceSource = cairo_create (dest);
938   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
939   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
940   else cairo_paint(pieceSource);
941   cairo_destroy (pieceSource);
942   if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
943 }
944
945 void
946 InsertPiece (AnimNr anr, ChessSquare piece)
947 {
948     CairoOverlayPiece(piece, c_animBufs[anr]);
949 }
950
951 void
952 DrawBlank (AnimNr anr, int x, int y, int startColor)
953 {
954     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
955 }
956
957 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
958                  int srcX, int srcY, int width, int height, int destX, int destY)
959 {
960         cairo_t *cr;
961         c_animBufs[anr+4] = CsBoardWindow(currBoard);
962         cr = cairo_create (c_animBufs[anr+destBuf]);
963         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
964         cairo_rectangle (cr, destX, destY, width, height);
965         cairo_fill (cr);
966         cairo_destroy (cr);
967         if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
968             GraphExpose(currBoard, destX, destY, width, height);
969 }
970
971 void
972 SetDragPiece (AnimNr anr, ChessSquare piece)
973 {
974 }
975
976 /* [AS] Arrow highlighting support */
977
978 void
979 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
980 {
981     cairo_t *cr;
982     int i;
983     cr = cairo_create (cs);
984     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
985     for (i=0;i<nr;i++) {
986         cairo_line_to(cr, arrow[i].x, arrow[i].y);
987     }
988     if(appData.monoMode) { // should we always outline arrow?
989         cairo_line_to(cr, arrow[0].x, arrow[0].y);
990         SetPen(cr, 2, "#000000", 0);
991         cairo_stroke_preserve(cr);
992     }
993     SetPen(cr, 2, appData.highlightSquareColor, 0);
994     cairo_fill(cr);
995
996     /* free memory */
997     cairo_destroy (cr);
998 }
999
1000 void
1001 DrawPolygon (Pnt arrow[], int nr)
1002 {
1003     DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1004 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1005 }
1006
1007 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1008
1009 static void
1010 ChoosePen(cairo_t *cr, int i)
1011 {
1012   switch(i) {
1013     case PEN_BLACK:
1014       SetPen(cr, 1.0, "#000000", 0);
1015       break;
1016     case PEN_DOTTED:
1017       SetPen(cr, 1.0, "#A0A0A0", 1);
1018       break;
1019     case PEN_BLUEDOTTED:
1020       SetPen(cr, 1.0, "#0000FF", 1);
1021       break;
1022     case PEN_BOLDWHITE:
1023       SetPen(cr, 3.0, crWhite, 0);
1024       break;
1025     case PEN_BOLDBLACK:
1026       SetPen(cr, 3.0, crBlack, 0);
1027       break;
1028     case PEN_BACKGD:
1029       SetPen(cr, 3.0, "#E0E0F0", 0);
1030       break;
1031   }
1032 }
1033
1034 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1035 void
1036 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1037 {
1038   static int curX, curY;
1039
1040   if(penType != PEN_NONE) {
1041     cairo_t *cr = cairo_create(CsBoardWindow(disp));
1042     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1043     cairo_move_to (cr, curX, curY);
1044     cairo_line_to (cr, x,y);
1045     ChoosePen(cr, penType);
1046     cairo_stroke (cr);
1047     cairo_destroy (cr);
1048   }
1049
1050   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1051   curX = x; curY = y;
1052 }
1053
1054 // front-end wrapper for drawing functions to do rectangles
1055 void
1056 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1057 {
1058   cairo_t *cr;
1059
1060   cr = cairo_create (CsBoardWindow(disp));
1061   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1062   cairo_rectangle (cr, left, top, right-left, bottom-top);
1063   switch(side)
1064     {
1065     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1066     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1067     case 2: ChoosePen(cr, PEN_BACKGD); break;
1068     }
1069   cairo_fill (cr);
1070
1071   if(style != FILLED)
1072     {
1073       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1074       ChoosePen(cr, PEN_BLACK);
1075       cairo_stroke (cr);
1076     }
1077
1078   cairo_destroy(cr);
1079 }
1080
1081 // front-end wrapper for putting text in graph
1082 void
1083 DrawEvalText (char *buf, int cbBuf, int y)
1084 {
1085     // the magic constants 8 and 5 should really be derived from the font size somehow
1086   cairo_text_extents_t extents;
1087   cairo_t *cr = cairo_create(CsBoardWindow(disp));
1088
1089   /* GTK-TODO this has to go into the font-selection */
1090   cairo_select_font_face (cr, "Sans",
1091                           CAIRO_FONT_SLANT_NORMAL,
1092                           CAIRO_FONT_WEIGHT_NORMAL);
1093   cairo_set_font_size (cr, 12.0);
1094
1095
1096   cairo_text_extents (cr, buf, &extents);
1097
1098   cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1099   cairo_text_path (cr, buf);
1100   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1101   cairo_fill_preserve (cr);
1102   cairo_set_source_rgb (cr, 0, 1.0, 0);
1103   cairo_set_line_width (cr, 0.1);
1104   cairo_stroke (cr);
1105
1106   /* free memory */
1107   cairo_destroy (cr);
1108 }