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