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