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