/*
 * GNUPLOT - caca.trm
 *
 * $Id: caca.trm,v 1.6.2.4 2016/03/22 16:48:34 markisch Exp $
 *
 * This file uses UTF8 encoding and a tab size of 4.
 *
 */

/*

Copyright (c) 2012-2014 Bastian Maerkisch. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#ifdef TERM_REGISTER
register_term(caca)
#endif

#ifdef TERM_PROTO

#define CACA_XMAX (80-1)
#define CACA_YMAX (25-1)

TERM_PUBLIC void CACA_options(void);
TERM_PUBLIC void CACA_init(void);
TERM_PUBLIC void CACA_graphics(void);
TERM_PUBLIC void CACA_text(void);
TERM_PUBLIC void CACA_reset(void);
TERM_PUBLIC void CACA_suspend(void);
TERM_PUBLIC void CACA_resume(void);
TERM_PUBLIC void CACA_linetype(int linetype);
TERM_PUBLIC int  CACA_make_palette(t_sm_palette *palette);
TERM_PUBLIC void CACA_color(t_colorspec *colorspec);
TERM_PUBLIC void CACA_move(unsigned int x, unsigned int y);
TERM_PUBLIC void CACA_point(unsigned int x, unsigned int y, int point);
TERM_PUBLIC void CACA_vector(unsigned int x, unsigned int y);
TERM_PUBLIC void CACA_layer(t_termlayer layer);
TERM_PUBLIC void CACA_path(int p);
TERM_PUBLIC void CACA_put_text(unsigned int x, unsigned int y,	const char *str);
TERM_PUBLIC void CACA_arrow(unsigned int sx, unsigned int sy,
				     unsigned int ex, unsigned int ey,
				     int head);
TERM_PUBLIC int  CACA_text_angle(int ang);
TERM_PUBLIC void CACA_fillbox(int style, unsigned int x, unsigned int y, unsigned int w, unsigned int h);
#ifdef USE_MOUSE
TERM_PUBLIC int  CACA_waitforinput(int);
TERM_PUBLIC void CACA_put_tmptext(int i, const char str[]);
TERM_PUBLIC void CACA_set_ruler(int, int);
TERM_PUBLIC void CACA_set_cursor(int, int, int);
#endif
TERM_PUBLIC void CACA_filled_polygon(int points, gpiPoint *corners);
TERM_PUBLIC void CACA_image(unsigned int M, unsigned int N, coordval *image,
					gpiPoint *corner, t_imagecolor color_mode);
TERM_PUBLIC void CACA_enhanced_put_text(unsigned int x, unsigned int y, const char str[]);
TERM_PUBLIC void CACA_enhanced_open(char * fontname, double fontsize,
					double base, TBOOLEAN widthflag, TBOOLEAN showflag, int overprint);
TERM_PUBLIC void CACA_enhanced_flush(void);
TERM_PUBLIC void CACA_hypertext(int type, const char *text);
#ifdef EAM_BOXED_TEXT
TERM_PUBLIC void CACA_boxed_text(unsigned int x, unsigned int y, int option);
#endif
TERM_PUBLIC void CACA_modify_plots(unsigned int operations, int plotno);

#endif /* TERM_PROTO */

/* This is a new section for public interfaces. */
#if defined(TERM_PUBLIC_PROTO) || defined (TERM_BODY)

extern TBOOLEAN CACA_process_events(void);
extern TBOOLEAN CACA_window_opened(void);

#endif /* TERM_PUBLIC_PROTO */

#ifdef TERM_BODY

#include <caca.h>
#include <wchar.h>
#include <signal.h>
#ifdef WIN32
# ifndef _WIN32_WINNT
#  define _WIN32_WINNT 0x0501
# endif
# include <windows.h>
#endif
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif

/* Internal Functions */

static void CACA_update_options(void); /* update the term_options string */
static void CACA_refresh(void); /* redraw the screen */
static void CACA_check_resize(void); /* test if the screen size changed */
static void CACA_init_angles(void); /* init the array used for rotated text */
static void CACA_close_display(void);
static void CACA_add_path_point(int x, int y); /* add a new point to current path */
static void CACA_init_display(void);
#ifdef USE_MOUSE
static void CACA_print_status(void); /* draw the status line */
static int CACA_translate_key(int key); /* map libcaca key codes to gnuplot's */
static unsigned long CACA_event_time(void); /* get the event time */
static void CACA_discard_events(void); /* discard pending caca events */
#endif
static void CACA_update_keybox(unsigned plotno, unsigned x, unsigned y);

/* libcaca helper functions */

/* CACA_put_transparent_char() attempts to 'merge' line drawing characters and does not
   change the background color. That way filled areas do not get overwritten. */
static void CACA_put_transparent_char(caca_canvas_t *cv, int x, int y, uint32_t c);

/* modified copies of libcaca functions which use CACA_put_transparent_char() */
static int CACA_put_str(caca_canvas_t *cv, int x, int y, char const *s);
static void CACA_draw_line(caca_canvas_t *cv, int x1, int y1, int x2, int y2, uint32_t ch);
static void CACA_draw_thin_line(caca_canvas_t *cv, int x1, int y1, int x2, int y2);
static void CACA_draw_thin_polyline(caca_canvas_t *cv, int const x[], int const y[], int n);
static int CACA_draw_cp437_box(caca_canvas_t *cv, int x, int y, int w, int h);
static int CACA_draw_thin_box(caca_canvas_t *cv, int x, int y, int w, int h);

static uint8_t CACA_rgb_to_ansi(uint32_t color); /* Convert RGB to ANSI background color */
static char const * CACA_get_color_name(uint8_t color); /* Find name of nearest matching predefined color */
static char * CACA_encode_text(const char *str); /* map strings according to current encoding */

#ifdef USE_MOUSE
static void process_event(char type, int mx, int my, int par1, int par2, int winid); /* wrapper for do_event() */
#endif
static int gp_kbhit(void); /* kbhit() implementation */


/* Types, Enums */

enum CACA_charset {
	CACA_ASCII = 0, CACA_BLOCKS, CACA_UNICODE
};

typedef struct {
	int x;
	int y;
} CACA_POINT;

typedef struct {
	int left;
	int right;
	int top;
	int bottom;
} CACA_RECT;

typedef struct CACA_HYPERTEXT_S {
	CACA_POINT loc;
	char *     text;
	struct CACA_HYPERTEXT_S * next;
} CACA_HYPERTEXT;


/* Internal Variables */

/* settings */
static int CACA_xmax = CACA_XMAX; /* terminal size, same as term->xmax */
static int CACA_ymax = CACA_YMAX;
static enum CACA_charset CACA_charset = CACA_BLOCKS;
static TBOOLEAN CACA_truecolor = FALSE; /* does backend support truecolor? */
static uint8_t CACA_background = CACA_WHITE; /* background color */
static char * CACA_driver = NULL; /* backend driver name */
static TBOOLEAN CACA_mono = FALSE; /* use greyscale only? */
static TBOOLEAN CACA_force_truecolor = FALSE;  /* override the terminals hard-coded truecolor setting */
static TBOOLEAN CACA_inverted = FALSE; /* use inverterted color (black background) */
static char * CACA_title = NULL; /* windows title */

static caca_display_t * CACA_dp;
static caca_canvas_t * CACA_cv = NULL;
static caca_canvas_t * CACA_screen_cv;
static caca_canvas_t * CACA_suspend_cv = NULL;
static TBOOLEAN CACA_windowed = FALSE; /* graphics output in different window*/
static TBOOLEAN CACA_exportonly = FALSE; /* no output to screen but to file */
static TBOOLEAN CACA_processing_events = FALSE; /* flag processing of events in CACA_text() */
static unsigned CACA_codepage = 0;
static unsigned TMP_codepage;

/* drawing */
static char CACA_pen;		/* current character used to draw point symbols */
static int CACA_x = -1;		/* current X position */
static int CACA_y = -1;		/* current Y position */
static int CACA_ang = 0;	/* current text angle */

/* newpath/closepath support */
static TBOOLEAN CACA_in_path = FALSE;
static int * CACA_path_x = NULL;
static int * CACA_path_y = NULL;
static int CACA_maxpath = 0;
static int CACA_polygon_points = 0;

static TBOOLEAN CACA_invertfill = TRUE; /* Switch foreground/background color for filling of polygons and boxes? */

/* mousing */
#ifdef USE_MOUSE
static const int CACA_status_height = 1; /* height of status line */
static CACA_POINT CACA_mouse; /* current mouse position */
static char * CACA_status_text = NULL; /* current status line text */
static int CACA_ruler_line = FALSE; /* status of ruler line */
static CACA_POINT CACA_ruler = {-1, -1}; /* ruler position */
static TBOOLEAN CACA_zooming = FALSE; /* mouse zooming */
static CACA_POINT CACA_zoom_from;
static char * CACA_zoom_from_text = NULL;
static CACA_POINT CACA_zoom_to;
static char * CACA_zoom_to_text = NULL;
#else
static const int CACA_status_height = 0;
#endif

/* boxed text */
#ifdef EAM_BOXED_TEXT
static struct s_boxedtext {
	TBOOLEAN   boxed;
	CACA_POINT margin;
	CACA_RECT  box;
	int        angle;
} CACA_boxedtext;
#endif

/* hypertext */
static CACA_HYPERTEXT * CACA_hypertext_list = NULL;
static CACA_HYPERTEXT * CACA_last_hypertext = NULL;

/* interactive hiding of plots */
static int CACA_plotno = 0;
static int CACA_maxhideplots = 0;
static TBOOLEAN * CACA_hideplot = NULL;
static int CACA_maxkeyboxes = 0;
static CACA_RECT * CACA_keyboxes;
static int CACA_skipplot = FALSE;
static int CACA_keysample = FALSE;
static TBOOLEAN CACA_zoom_or_replot = FALSE; /* do not reset above variables when zooming/replotting */

/* colors */
static const char CACA_AXIS_CONST = '\1';
static const char CACA_BORDER_CONST = '\2';
static const int CACA_colors[9] = {
	CACA_BLACK, CACA_LIGHTGRAY,
	CACA_LIGHTRED, CACA_LIGHTGREEN, CACA_LIGHTBLUE, CACA_LIGHTMAGENTA,
	CACA_LIGHTCYAN, CACA_BROWN, CACA_BLACK
};

/* Unicode light box drawing characters */
enum CACA_box_drawing_chars {
	BOX_HOR        = 0x2500,
	BOX_VERT       = 0x2502,
	BOX_UP_LEFT    = 0x2518,
	BOX_UP_HOR     = 0x2534,
	BOX_UP_RIGHT   = 0x2514,
	BOX_VERT_LEFT  = 0x2524,
	BOX_VERT_HOR   = 0x253c,
	BOX_VERT_RIGHT = 0x251c,
	BOX_DOWN_LEFT  = 0x2510,
	BOX_DOWN_HOR   = 0x252c,
	BOX_DOWN_RIGHT = 0x250c
};

/* option names */
enum CACA_id { CACA_DEFAULTOPTIONS,
	CACA_DRIVER, CACA_FORMAT,
	CACA_SIZE, CACA_TITLE,
	CACA_COLOR, CACA_MONOCHROME,
	CACA_TRUECOLOR, CACA_NOTRUECOLOR,
	CACA_INVERTED, CACA_NOINVERTED,
	CACA_CHARSET, CACA_BACKGROUND,
	CACA_ENH, CACA_NOENH,
	CACA_OTHER };

static struct gen_table CACA_opts[] =
{
    { "def$ault", CACA_DEFAULTOPTIONS },
    { "driver", CACA_DRIVER },
    { "format", CACA_FORMAT },
    { "size", CACA_SIZE },
    { "ti$tle", CACA_TITLE },
    { "c$olor", CACA_COLOR },
    { "c$olour", CACA_COLOR },
    { "m$onochrome", CACA_MONOCHROME },
    { "inv$erted", CACA_INVERTED },
    { "noinv$erted", CACA_NOINVERTED },
    { "ch$arset", CACA_CHARSET },
    { "backg$round", CACA_BACKGROUND },
    { "enh$anced", CACA_ENH },
    { "noe$nhanced", CACA_NOENH },
    { "tru$ecolor", CACA_TRUECOLOR },
    { "notru$ecolor", CACA_NOTRUECOLOR },
    { NULL, CACA_OTHER }
};


TERM_PUBLIC void
CACA_options()
{
	while (!END_OF_COMMAND) {
		switch ((enum CACA_id) lookup_table(&CACA_opts[0], c_token)) {
			case CACA_DEFAULTOPTIONS:
				term->put_text = CACA_enhanced_put_text;
				term->flags |= TERM_ENHANCED_TEXT;
				term->flags |= TERM_NO_OUTPUTFILE;
				term->flags &= ~TERM_MONOCHROME;
				free(CACA_driver);
				CACA_driver = NULL;
				free(CACA_title);
				CACA_title = NULL;
				CACA_background = CACA_WHITE;
				CACA_charset = CACA_BLOCKS;
				term->xmax = CACA_xmax = CACA_XMAX;
				term->ymax = CACA_ymax = CACA_YMAX;
				CACA_mono = FALSE;
				CACA_inverted = FALSE;
				break;
			case CACA_ENH:
				c_token++;
				term->put_text = CACA_enhanced_put_text;
				term->flags |= TERM_ENHANCED_TEXT;
				break;
			case CACA_NOENH:
				c_token++;
				term->put_text = CACA_put_text;
				term->flags &= ~TERM_ENHANCED_TEXT;
				break;
			case CACA_CHARSET: {
				c_token++;
				/* Character set to use for fills, lines and points */
				if (equals(c_token, "ascii"))
					CACA_charset = CACA_ASCII;
				else if (equals(c_token, "blocks"))
					CACA_charset = CACA_BLOCKS;
				else if (equals(c_token, "unicode"))
					CACA_charset = CACA_UNICODE;
				else if (!END_OF_COMMAND)
					int_error(c_token, "Unknown character set.");
				else
					int_error(c_token, "Option `charset` requires an argument, see `help caca`.");
				c_token++;
				break;
			}
			case CACA_BACKGROUND: {
				uint32_t CACA_background_rgb;

				c_token++;
				CACA_background_rgb = parse_color_name();
				CACA_background = CACA_rgb_to_ansi(CACA_background_rgb);
				FPRINTF((stderr, "CACA: background color = %06x, bg = %0x name = %s\n",
					CACA_background_rgb, CACA_background, CACA_get_color_name(CACA_background)));
				break;
			}
			case CACA_FORMAT:
			case CACA_DRIVER: {
				char const * const * format_list;
				int i;

				c_token++;
				if (equals(c_token, "default")) {
					c_token++;
					CACA_close_display();
					free(CACA_driver);
					CACA_driver = NULL;
				} else if (equals(c_token, "list")) {
					c_token++;
					fprintf(stderr, "caca drivers: ");
					format_list = caca_get_display_driver_list();
					for (i = 0; format_list[i] != NULL; i += 2)
						fprintf(stderr, "%s ", format_list[i]);
					fprintf(stderr, "\ncaca export formats: ");
					format_list = caca_get_export_list();
					for (i = 0; format_list[i] != NULL; i += 2)
						fprintf(stderr, "%s ", format_list[i]);
					fprintf(stderr, "\n");
					return;
				} else {
					if (!END_OF_COMMAND) {
						char * driver = NULL;
						m_capture(&driver, c_token, c_token);
						c_token++;
						/* Has driver changed? */
						if ((CACA_driver != NULL) && (strcmp(CACA_driver, driver) != 0))
							CACA_close_display();
						free(CACA_driver);
						CACA_driver = driver;
					} else {
						int_error(c_token, "Option `driver` requires an argument, see `help caca`.");
					}
				}

				/* Test the list of export formats for a match. In that case
				we do not display anything but save the graph to a file. */
				CACA_exportonly = FALSE;
				if (CACA_driver != NULL) {
					format_list = caca_get_export_list();
					for (i = 0; format_list[i] != NULL; i += 2) {
						if (strcmp(format_list[i], CACA_driver) == 0) {
							CACA_exportonly = TRUE;
							break;
						}
					}
				}

				/* Verify that the driver is valid. If it is an export format
				it was detected already above. */
				if ((CACA_driver != NULL) && !CACA_exportonly) {
					TBOOLEAN found = FALSE;
					format_list = caca_get_display_driver_list();
					for (i = 0; format_list[i] != NULL; i += 2) {
						if (strcmp(format_list[i], CACA_driver) == 0) {
							found = TRUE;
							break;
						}
					}
					if (!found) {
						free(CACA_driver);
						CACA_driver = NULL;
						int_error(c_token, "Unknown driver or export format.");
					}
				}

				if (CACA_exportonly)
					term->flags &= ~TERM_NO_OUTPUTFILE;
				else
					term->flags |= TERM_NO_OUTPUTFILE;
				break;
			}
			case CACA_COLOR:
				c_token++;
				CACA_mono = FALSE;
				term->flags &= ~TERM_MONOCHROME;
				break;
			case CACA_MONOCHROME:
				c_token++;
				CACA_mono = TRUE;
				term->flags |= TERM_MONOCHROME;
				break;
			case CACA_INVERTED:
				c_token++;
				CACA_inverted = TRUE;
				CACA_background = CACA_BLACK;
				break;
			case CACA_NOINVERTED:
				c_token++;
				CACA_inverted = FALSE;
				CACA_background = CACA_WHITE;
				break;
			case CACA_TRUECOLOR:
				c_token++;
				CACA_force_truecolor = TRUE;
				break;
			case CACA_NOTRUECOLOR:
				c_token++;
				CACA_force_truecolor = FALSE;
				break;
			case CACA_SIZE: {
				float width, height;

				c_token++;
				parse_term_size(&width, &height, PIXELS);
				term->xmax = CACA_xmax = width - 1;
				term->ymax = CACA_ymax = height - 1;
				break;
			}
			case CACA_TITLE:
				c_token++;
				if (!END_OF_COMMAND) {
					free(CACA_title);
					CACA_title = strdup(try_to_get_string());
				} else {
					int_error(c_token, "Option `title` requires an argument, see `help caca`.");
				}
				break;
			case CACA_OTHER:
			default:
				int_error(c_token, "extraneous argument in set terminal %s", term->name);
				break;
		}
	}

	CACA_update_options();
#ifdef DEBUG
	{
		int i;
		for (i = 0; i < 16; i++) {
			fprintf(stderr, "CACA: color #%i = %s\n", i, CACA_get_color_name(i));
		}
	}
#endif
}


static void
CACA_update_options(void)
{
	char options[MAX_LINE_LEN + 1];
	int n;

	term_options[0] = NUL;
	if (CACA_driver != NULL)
		snprintf(term_options, MAX_LINE_LEN + 1, "%s %s ", CACA_exportonly ? "format" : "driver", CACA_driver);
	if (CACA_title != NULL) {
		n = snprintf(options, MAX_LINE_LEN, "title \"%s\" ", CACA_title);
		if (n >= MAX_LINE_LEN) options[MAX_LINE_LEN] = NUL;
		strcat(term_options, options);
	}
	n = snprintf(options, MAX_LINE_LEN + 1, "%senhanced size %d, %d background rgb \"%s\" %s %s ",
		(term->flags & TERM_ENHANCED_TEXT) ? "" : "no",
		CACA_xmax + 1, CACA_ymax + 1,
		CACA_get_color_name(CACA_background),
		CACA_mono ? "monochrome" : "color",
		CACA_inverted ? "inverted" : "noinverted");
	if (n >= MAX_LINE_LEN) options[MAX_LINE_LEN] = NUL;
	strcat(term_options, options);
	strcat(term_options, "charset ");
	switch (CACA_charset) {
		case CACA_ASCII:
			strcat(term_options, "ascii");
			break;
		case CACA_BLOCKS:
			strcat(term_options, "blocks");
			break;
		case CACA_UNICODE:
			strcat(term_options, "unicode");
			break;
	}
}


TERM_PUBLIC void
CACA_init()
{
	CACA_init_display();
	if (CACA_cv != NULL)
		caca_clear_canvas(CACA_cv);
	if (CACA_dp != NULL) {
#ifdef USE_MOUSE
		CACA_print_status();
#endif
		caca_refresh_display(CACA_dp);
		CACA_check_resize();
	}
}


static void
CACA_refresh(void)
{
	CACA_HYPERTEXT * hypertext = CACA_hypertext_list;

	if ((CACA_dp == NULL) || CACA_exportonly)
		return;

	/* copy graph to screen canvas */
	caca_blit(CACA_screen_cv, 0, 0, CACA_cv, NULL);

#ifdef USE_MOUSE
	/* hypertext */
	while (hypertext) {
		if ((CACA_mouse.x == hypertext->loc.x) && (CACA_mouse.y == hypertext->loc.y)) {
			uint32_t attr = caca_get_attr(CACA_screen_cv, -1, -1);
			caca_set_color_ansi(CACA_screen_cv, CACA_background, !CACA_inverted ? CACA_BLACK : CACA_WHITE);
			caca_put_str(CACA_screen_cv,
						CACA_mouse.x + 1, CACA_ymax - CACA_mouse.y,
						hypertext->text);
			caca_set_attr(CACA_screen_cv, attr);
			break;
		}
		hypertext = hypertext->next;
	}

	/* draw ruler */
	if (CACA_ruler.x >= 0) {
		uint32_t attr = caca_get_attr(CACA_screen_cv, -1, -1);
		caca_set_color_ansi(CACA_screen_cv, !CACA_inverted ? CACA_BLACK : CACA_WHITE, CACA_background);
		if (CACA_charset == CACA_ASCII) {
			CACA_draw_thin_line(CACA_screen_cv,
								CACA_ruler.x, 0, CACA_ruler.x, CACA_ymax);
			CACA_draw_thin_line(CACA_screen_cv, 0,
								CACA_ymax - CACA_ruler.y, CACA_xmax,
								CACA_ymax - CACA_ruler.y);
			CACA_put_transparent_char(CACA_screen_cv, CACA_ruler.x, CACA_ymax - CACA_ruler.y, L'+');
		} else { /* BLOCKS / UNICODE */
			CACA_draw_line(CACA_screen_cv,
								CACA_ruler.x, 0, CACA_ruler.x, CACA_ymax, BOX_VERT);
			CACA_draw_line(CACA_screen_cv, 0,
								CACA_ymax - CACA_ruler.y, CACA_xmax,
								CACA_ymax - CACA_ruler.y, BOX_HOR);
			CACA_put_transparent_char(CACA_screen_cv, CACA_ruler.x, CACA_ymax - CACA_ruler.y, BOX_VERT_HOR);
		}
		/* ruler line */
		if (CACA_ruler_line) {
			CACA_draw_thin_line(CACA_screen_cv,
								CACA_ruler.x, CACA_ymax - CACA_ruler.y,
								CACA_mouse.x, CACA_ymax - CACA_mouse.y);
		}
		caca_set_attr(CACA_screen_cv, attr);
	}

	/* draw zoom box */
	if (CACA_zooming) {
		if (CACA_charset == CACA_ASCII)
			caca_draw_thin_box(CACA_screen_cv,
							CACA_zoom_from.x, CACA_ymax - CACA_zoom_from.y,
							CACA_zoom_to.x - CACA_zoom_from.x + 1,
							CACA_zoom_from.y - CACA_zoom_to.y + 1);
		else /* BLOCKS / UNICODE */
			caca_draw_cp437_box(CACA_screen_cv,
							CACA_zoom_from.x, CACA_ymax - CACA_zoom_from.y,
							CACA_zoom_to.x - CACA_zoom_from.x + 1,
							CACA_zoom_from.y - CACA_zoom_to.y + 1);
		if (CACA_zoom_from_text) {
			char *separator = strchr(CACA_zoom_from_text, '\r');
			if (separator) {
				*separator = NUL;
				caca_put_str(CACA_screen_cv,
							CACA_zoom_from.x + 1, CACA_ymax - CACA_zoom_from.y - 1,
							CACA_zoom_from_text);
				caca_put_str(CACA_screen_cv,
							CACA_zoom_from.x + 1, CACA_ymax - CACA_zoom_from.y,
							separator + 1);
				*separator = '\r';
			} else {
				caca_put_str(CACA_screen_cv,
							CACA_zoom_from.x + 1, CACA_ymax - CACA_zoom_from.y - 1,
							CACA_zoom_from_text);
			}
		}
		if (CACA_zoom_to_text) {
			char *separator = strchr(CACA_zoom_to_text, '\r');
			if (separator) {
				*separator = NUL;
				caca_put_str(CACA_screen_cv,
							CACA_zoom_to.x + 1, CACA_ymax - CACA_zoom_to.y - 1,
							CACA_zoom_to_text);
				caca_put_str(CACA_screen_cv,
							CACA_zoom_to.x + 1, CACA_ymax - CACA_zoom_to.y,
							separator + 1);
				*separator = '\r';
			} else {
				caca_put_str(CACA_screen_cv,
							CACA_zoom_to.x + 1, CACA_ymax - CACA_zoom_to.y - 1,
							CACA_zoom_to_text);
			}
		}
	}

	/* status line */
	CACA_print_status();
#endif

	/* update display */
	caca_refresh_display(CACA_dp);
}


static void
CACA_check_resize()
{
	caca_event_t ev;

	if ((CACA_dp == NULL) || CACA_exportonly)
		return;

	if (caca_get_event(CACA_dp, CACA_EVENT_RESIZE, &ev, 100)) {
		FPRINTF((stderr, "CACA: resize event detected in check_resize().\n"));
		term->xmax = CACA_xmax = caca_get_event_resize_width(&ev) - 1;
		term->ymax = CACA_ymax = caca_get_event_resize_height(&ev) - 1 - CACA_status_height;
		caca_set_canvas_size(CACA_cv, term->xmax + 1, term->ymax + 1);
		CACA_update_options();
		CACA_refresh();
	} else if ((caca_get_canvas_width(CACA_screen_cv) != term->xmax + 1) ||
	           (caca_get_canvas_height(CACA_screen_cv) != term->ymax + 1 + CACA_status_height)) {
		/* Did we miss a resize event? */
		FPRINTF((stderr, "CACA: missed resize event in check_resize().\n"));
		term->xmax = CACA_xmax = caca_get_canvas_width(CACA_screen_cv) - 1;
		term->ymax = CACA_ymax = caca_get_canvas_height(CACA_screen_cv) - 1 - CACA_status_height;
		caca_set_canvas_size(CACA_cv, term->xmax + 1, term->ymax + 1);
		CACA_update_options();
		CACA_refresh();
	}
}


void
CACA_sigint_handler(int sig)
{
	if (!CACA_windowed)
		CACA_close_display();
	interrupt_setup();
	raise(SIGINT);
}


static void
CACA_init_display(void)
{
	if ((CACA_dp == NULL) && !CACA_exportonly) {
		if (CACA_driver == NULL)
			CACA_dp = caca_create_display(NULL);
		else if (!CACA_exportonly)
			CACA_dp = caca_create_display_with_driver(NULL, CACA_driver);
	}

	if (CACA_dp != NULL) {
		if (CACA_driver == NULL)
			CACA_driver = strdup(caca_get_display_driver(CACA_dp));
		CACA_windowed = (strcmp(CACA_driver, "x11") == 0);
		CACA_windowed |= (strcmp(CACA_driver, "gl") == 0);
#if defined(WIN32) && !defined(WGP_CONSOLE)
		CACA_windowed |= (strcmp(CACA_driver, "win32") == 0);
#endif
#ifdef USE_MOUSE
		if (CACA_windowed)
			term->waitforinput = CACA_waitforinput;
		else
			term->waitforinput = NULL;
		caca_set_mouse(CACA_dp, 1);
#endif
		/* NOTE: Info on truecolor support is not available from libcaca and
		is thus hard-coded here. The checks below have to be adopted
		whenever libcaca evolves. */
		CACA_truecolor = (strcmp(CACA_driver, "x11") == 0);
		CACA_truecolor |= (strcmp(CACA_driver, "gl") == 0);
		CACA_truecolor |= CACA_force_truecolor;
		FPRINTF((stderr, "CACA: driver = '%s'\n", CACA_driver));
		caca_set_display_title(CACA_dp,
			(CACA_title != NULL) ? CACA_title : "gnuplot: caca terminal");
		CACA_screen_cv = caca_get_canvas(CACA_dp);
		term->xmax = CACA_xmax = caca_get_canvas_width(CACA_screen_cv) - 1;
		term->ymax = CACA_ymax = caca_get_canvas_height(CACA_screen_cv) - 1 - CACA_status_height;
		CACA_update_options();

		if ((strcmp(CACA_driver, "x11") == 0) || (strcmp(CACA_driver, "ncurses") == 0)) {
			/* NOTE: The ncurses and x11 backends change LC_ALL. We try to
			(partially) revert this again: */
			setlocale(LC_TIME, current_locale != NULL ? current_locale : "C");
			setlocale(LC_NUMERIC, numeric_locale != NULL ? numeric_locale : "C");
		}
		if (strcmp(CACA_driver, "slang") == 0) {
			/* NOTE: The slang backend driver sets its own signal handler for SIGINT */
			signal(SIGINT, CACA_sigint_handler);
		}
#ifdef WIN32
		if (strcmp(CACA_driver, "win32") == 0) {
			/* On CJK Windows we change the codepage. Not sure why this
			   solves the display problems, but apparently it works. See bug #1361. */
			if ((TMP_codepage = GetConsoleOutputCP()) == 932
				|| TMP_codepage == 936
				|| TMP_codepage == 950) {
				CACA_codepage = TMP_codepage;
				SetConsoleOutputCP(437);
			}
		}
#endif
	} else if (CACA_exportonly) {
		/* NOTE: Info on truecolor support is not available from libcaca and
		is thus hard-coded here. The checks below have to be adopted
		whenever libcaca evolves. */
		CACA_truecolor = TRUE; /* caca, html, html3, (bbfr,) ps, svg */
		if ((strcmp(CACA_driver, "troff") == 0) ||
			(strcmp(CACA_driver, "tga") == 0))
			CACA_truecolor = FALSE;
		CACA_truecolor |= CACA_force_truecolor;
		CACA_windowed = FALSE;
#ifdef USE_MOUSE
		term->waitforinput = NULL;
#endif
	} else {
		/* The driver initialization by libcaca failed. */
		fprintf(stderr, "ERROR: Failed to initialize libcaca %s driver!\n", (CACA_driver != NULL) ? CACA_driver : "default");
		CACA_windowed = FALSE;
#ifdef USE_MOUSE
		term->waitforinput = NULL;
#endif
		CACA_truecolor = FALSE;
	}
	FPRINTF((stderr, "CACA_driver = %s, truecolor = %i\n", CACA_driver, CACA_truecolor));

	CACA_cv = caca_create_canvas(term->xmax + 1, term->ymax + 1);
	caca_set_color_ansi(CACA_cv, !CACA_inverted ? CACA_BLACK : CACA_WHITE, CACA_background);
}


static void
CACA_init_aspect_ratio(void)
{
	int width = 6;
	int height = 10;

	if (CACA_dp) {
		height = caca_get_display_height(CACA_dp) / (CACA_ymax + CACA_status_height + 1);
		width = caca_get_display_width(CACA_dp) / (CACA_xmax + 1);
	}
	if (height > width) {
		term->h_tic = (height + width / 2) / width;
		term->v_tic = 1;
	} else {
		term->h_tic = 1;
		term->v_tic = (width + height / 2) / height;
	}
}


TERM_PUBLIC void
CACA_graphics()
{
	CACA_HYPERTEXT * hypertext;

	if (CACA_dp == NULL) {
		CACA_init();
		if (CACA_cv == NULL)
			fprintf(stderr, "ERROR: Failed to setup up libcaca canvas!\n");
	}
	if (CACA_cv == NULL)
		return;

	caca_set_color_ansi(CACA_cv, !CACA_inverted ? CACA_BLACK : CACA_WHITE, CACA_background);
	caca_clear_canvas(CACA_cv);
	if (CACA_dp != NULL) {
#ifdef USE_MOUSE
		CACA_print_status();
#endif
		CACA_check_resize();
	}
	FPRINTF((stderr, "CACA: terminal size %i x %i\n", CACA_xmax + 1, CACA_ymax + 1 + CACA_status_height));
	CACA_init_angles();
	CACA_init_aspect_ratio();
	CACA_x = -1;
	CACA_y = -1;

	/* clear hypertext labels */
	hypertext = CACA_hypertext_list;
	while (hypertext != NULL) {
		CACA_HYPERTEXT * next = hypertext->next;
		free(hypertext->text);
		free(hypertext);
		hypertext = next;
	};
	CACA_hypertext_list = NULL;
	CACA_last_hypertext = NULL;

	/* Reset layer variables */
	CACA_plotno = 0;
	CACA_skipplot = FALSE;
	CACA_keysample = FALSE;
	if (CACA_zoom_or_replot) {
		CACA_zoom_or_replot = FALSE;
	} else {
		int i;

		for (i = 0; i < CACA_maxhideplots; i++)
			CACA_hideplot[i] = FALSE;
		for (i = 0; i < CACA_maxkeyboxes; i++) {
			CACA_keyboxes[i].left = INT_MAX;
			CACA_keyboxes[i].right = 0;
			CACA_keyboxes[i].bottom = INT_MAX;
			CACA_keyboxes[i].top = 0;
		}
	}
}


TERM_PUBLIC void
CACA_text()
{
	if (CACA_dp != NULL)
		CACA_refresh();
#ifdef USE_MOUSE
	process_event(GE_plotdone, 0, 0, 0, 0, 0);
	if (!CACA_windowed && !CACA_processing_events && !CACA_exportonly) {
		/* NOTE: This is a hack to force a call to _graphics() before redrawing.
		   The vgagl terminal uses the very same trick. */
		term_graphics = FALSE;
		CACA_processing_events = TRUE;
		while (CACA_process_events());
		/* Clean up remaining console events, as e.g. KEYUP events: */
		CACA_discard_events();
		CACA_processing_events = FALSE;
		term_graphics = TRUE;
	}
#endif
	/* Save canvas to file. */
	if (CACA_exportonly) {
		size_t bytes;
#ifndef USE_CACA_EXPORT_MEMORY
		/* available since 0.99.beta17 */
		void * data = caca_export_canvas_to_memory(CACA_cv, CACA_driver, &bytes);
#else
		void * data = caca_export_memory(CACA_cv, CACA_driver, &bytes);
#endif
		fwrite(data, 1, bytes, gpoutfile);
		free(data);
	}
#if defined(WIN32) && !defined(WGP_CONSOLE)
	/* Raise the console (graph!) window. Beware: */
	/* we cannot call RaiseConsole() to do this.  */
	else {
		HWND console = GetConsoleWindow();
		if (console != NULL) {
			ShowWindow(console, SW_SHOWNORMAL);
			BringWindowToTop(console);
			SetFocus(console);
		}
	}
#endif
}


static void
CACA_close_display(void)
{
	/* make a copy of the current canvas */
	if (CACA_dp != NULL) {
		if (!CACA_windowed) {
			if (CACA_suspend_cv != NULL)
				caca_free_canvas(CACA_suspend_cv);
			CACA_suspend_cv = caca_create_canvas(CACA_xmax + 1, CACA_ymax + 1 + CACA_status_height);
			caca_blit(CACA_suspend_cv, 0, 0, CACA_cv, NULL);
		}
		caca_free_canvas(CACA_cv);
		caca_set_display_title(CACA_dp, "gnuplot");
		caca_free_display(CACA_dp);
		CACA_dp = NULL;
#ifdef WIN32
		if (strcmp(CACA_driver, "win32") == 0) {
#ifndef WGP_CONSOLE
			FreeConsole(); /* close the console window */
#endif
			/* Revert to previous codepage if necessary */
			if (CACA_codepage != 0)
				SetConsoleOutputCP(CACA_codepage);
		}
#ifndef WGP_CONSOLE
		WinPersistTextClose();
#endif
#endif
		/* NOTE: The slang backend driver sets its own signal handler for SIGINT,
		   but does not seem to restore the previous handler on exit.
		 */
		if (strcmp(CACA_driver, "slang") == 0)
			interrupt_setup();
	}
}


TERM_PUBLIC void
CACA_suspend(void)
{
	if (CACA_exportonly)
		return;

	CACA_text();
	if (!CACA_windowed)
		CACA_close_display();
}


TERM_PUBLIC void
CACA_resume(void)
{
	if (CACA_exportonly)
		return;

	if (CACA_dp == NULL) {
		CACA_init_display();
		if (CACA_cv == NULL) {
			fprintf(stderr, "ERROR: Failed to setup up libcaca canvas!\n");
			return;
		}
		if (CACA_suspend_cv != NULL) {
			caca_blit(CACA_cv, 0, 0, CACA_suspend_cv, NULL);
			caca_free_canvas(CACA_suspend_cv);
			CACA_suspend_cv = NULL;
		}
	}
#ifdef USE_MOUSE
	CACA_print_status();
#endif
	CACA_refresh();
	CACA_check_resize();
}


TERM_PUBLIC void
CACA_reset()
{
#ifdef USE_MOUSE
	free(CACA_status_text);
	CACA_status_text = NULL;
	free(CACA_zoom_from_text);
	free(CACA_zoom_to_text);
	CACA_zoom_from_text = CACA_zoom_to_text = NULL;
#endif
	free(CACA_path_x);
	free(CACA_path_y);
	CACA_path_x = CACA_path_y = NULL;
	CACA_polygon_points = CACA_maxpath = 0;
}


TERM_PUBLIC void
CACA_linetype(int linetype)
{
	static const char pen_type[] = "*#$%@&=";
	t_colorspec colorspec;

	if (linetype == LT_BLACK)
		CACA_pen = CACA_BORDER_CONST;
	else if (linetype == LT_AXIS)
		CACA_pen = CACA_AXIS_CONST;
	else if (linetype <= LT_NODRAW)
		CACA_pen = ' ';
	else
		/* Only required for mono output. */
		CACA_pen = pen_type[linetype % strlen(pen_type)];

	colorspec.type = TC_LT;
	colorspec.lt = linetype;
	CACA_color(&colorspec);
}


TERM_PUBLIC int
CACA_make_palette(t_sm_palette *palette)
{
	/* Terminal has 'continuous' colour support. */
	return 0;
}


TERM_PUBLIC void
CACA_color(t_colorspec *colorspec)
{
	if (CACA_cv == NULL)
		return;

	/* NOTE: Although libcaca offers the CACA_TRANSPARENT attribute, it does
	currently not support transparent lines. */
	switch (colorspec->type) {
		case TC_LT: {
			if (!CACA_mono) {
				if (colorspec->lt >= LT_BLACK) {
					uint16_t color = CACA_colors[(colorspec->lt + 2) % 9];
					if (CACA_inverted && (color == CACA_BLACK))
						color = CACA_WHITE;
					caca_set_color_ansi(CACA_cv, color, CACA_background);
				} else /* LT_BACKGROUND */
					caca_set_color_ansi(CACA_cv, CACA_background, CACA_background);
			} else {
				if (colorspec->lt == LT_AXIS)
					caca_set_color_ansi(CACA_cv, CACA_LIGHTGRAY, CACA_background);
				else if (colorspec->lt >= LT_BLACK)
					caca_set_color_ansi(CACA_cv, !CACA_inverted ? CACA_BLACK : CACA_WHITE, CACA_background);
				else /* LT_BACKGROUND */
					caca_set_color_ansi(CACA_cv, CACA_background, CACA_background);
			}
			break;
		}
		case TC_FRAC: {
			uint32_t attr;
			rgb255_color rgb255;
			uint16_t alpha;
			uint16_t fg_color;
			uint16_t bg_color;

			/* Immediately translate palette index to RGB colour */
			rgb255maxcolors_from_gray(colorspec->value, &rgb255);
			alpha = 0xf;

			/* keep background color */
			attr = caca_get_attr(CACA_cv, -1, -1); 	/* get current attribute */

			if (!CACA_truecolor) {
				uint32_t fg_color32;
				bg_color = caca_attr_to_ansi_bg(attr);
				if (CACA_mono) {
					/* convert to gray */
					int luma = (int)(rgb255.r * 0.3 + rgb255.g * 0.59 + rgb255.b * 0.11);
					fg_color32 = (0xff << 24) | (luma << 16) | (luma << 8) | luma;
				} else {
					fg_color32 = (0xff << 24) | (rgb255.r << 16) | (rgb255.g << 8) | rgb255.g;
				}
				fg_color = CACA_rgb_to_ansi(fg_color32);
				caca_set_color_ansi(CACA_cv, fg_color, bg_color);
			} else {
				bg_color = caca_attr_to_rgb12_bg(attr);
				bg_color |= 0xf000;
				if (CACA_mono) {
					/* convert to gray */
					int luma = (int)(rgb255.r * 0.3 + rgb255.g * 0.59 + rgb255.b * 0.11);
					fg_color = ((alpha >> 4) << 12) |
								((luma >> 4) << 8) |
								((luma >> 4) << 4) |
								 (luma >> 4);
				} else {
					/* caca uses 4 bits per component only */
					fg_color = ((alpha >> 4) << 12) |
								((rgb255.r >> 4) << 8) |
								((rgb255.g >> 4) << 4) |
								 (rgb255.b >> 4);
				}
				caca_set_color_argb(CACA_cv, fg_color, bg_color);
			}
			break;
		}
		case TC_RGB: {
			uint32_t attr;
			uint16_t alpha, red, green, blue;
			uint16_t fg_color;
			uint16_t bg_color;

			/* keep background color */
			attr = caca_get_attr(CACA_cv, -1, -1); 	/* get current attribute */

			/* extract color components from colorspec.lt */
			alpha = 0xff - ((colorspec->lt >> 24) & 0xff);
			red = (colorspec->lt >> 16) & 0xff;
			green = (colorspec->lt >> 8) & 0xff;
			blue = (colorspec->lt) & 0xff;

			if (!CACA_truecolor) {
				uint32_t fg_color32 = colorspec->lt;
				bg_color = caca_attr_to_ansi_bg(attr);
				if (CACA_mono) {
					/* convert to gray */
					int luma = (int)(red * 0.3 + green * 0.59 + blue * 0.11);
					fg_color32 = (0xff << 24) | (luma << 16) | (luma << 8) | luma;
				}
				fg_color = CACA_rgb_to_ansi(fg_color32);
				caca_set_color_ansi(CACA_cv, fg_color, bg_color);
			} else {
				bg_color = caca_attr_to_rgb12_bg(attr);
				bg_color |= 0xf000;

				if (CACA_mono) {
					/* convert to gray */
					int luma = (int)(red * 0.3 + green * 0.59 + blue * 0.11);
					fg_color = ((alpha >> 4) << 12) |
								((luma >> 4) << 8) |
								((luma >> 4) << 4) |
								 (luma >> 4);
				} else {
					/* caca uses 4 bits per component only */
					fg_color = ((alpha >> 4) << 12) |
								((red >> 4) << 8) |
								((green >> 4) << 4) |
								 (blue >> 4);
				}
				caca_set_color_argb(CACA_cv, fg_color, bg_color);
			}
			break;
		}
	}
}


TERM_PUBLIC void
CACA_move(unsigned int x, unsigned int y)
{
	if (CACA_in_path && ((CACA_x != x) || (CACA_y != y)))
		CACA_add_path_point(x, y);
	CACA_x = x;
	CACA_y = y;
}


TERM_PUBLIC void
CACA_point(unsigned int x, unsigned int y, int point)
{
	const wchar_t usymbol[] = { /* 0x2022 */
		0x2219 /* bullet */, L'+', 0x00d7 /* multiplication sign */,
		0x0263c, 0x25a0 /* black square */, 0x25cb /* white circle */, 0x25ca /* lozenge */,
		0x25b2 /* black up triangle */, 0x25bc /* black down triangle */,
		0x2302 /* house */,
		0x263a /* white smiling face */, 0x263b /* black smiling face */,
		0x2640 /* female sign */, 0x2642 /* male sign */,
		0x2660, 0x2663, 0x2665, 0x2666,
		0x266a /* eighth note */, 0x266b /* beamed eighth notes */
	};
	const int nsymbols = (sizeof(usymbol) / sizeof(wchar_t)) - 1;
	wchar_t symbol;

	if (CACA_cv == NULL)
		return;

	if (CACA_skipplot && !CACA_keysample)
		return;

	if (CACA_charset == CACA_ASCII)
		symbol = (point <= -1) ? L'.' : point % 26 + 'A';
	else
		symbol = (point < -1) ? 0x2219 /* L'∙' */ : usymbol[(point + 1) % nsymbols];
	caca_put_char(CACA_cv, x, CACA_ymax - y, symbol);

	/* update last hypertext label */
	if (CACA_last_hypertext != NULL) {
		CACA_last_hypertext->loc.x = x;
		CACA_last_hypertext->loc.y = y;
		CACA_last_hypertext = NULL;
	}

	if (CACA_keysample) {
		CACA_update_keybox(CACA_plotno, x, y);
	}
}


TERM_PUBLIC void
CACA_vector(unsigned int arg_x, unsigned int arg_y)
{
	int x = arg_x;
	int y = arg_y;
	wchar_t pen = NUL;

	if (CACA_cv == NULL)
		return;

	if (CACA_skipplot && !CACA_keysample)
		return;

	if (CACA_in_path) {
		CACA_add_path_point(x, y);
		CACA_x = x;
		CACA_y = y;
		return;
	}

	/* Use line drawing characters for border and axis. */
	if ((CACA_charset != CACA_ASCII) &&
		((CACA_pen == CACA_BORDER_CONST) || (CACA_pen == CACA_AXIS_CONST))) {
		if (CACA_y == y) /* horizontal line */
			pen = BOX_HOR;
		else if (CACA_x == x) /* vertical line */
			pen = BOX_VERT;
	}
	/* Use monochrome pen if applicable. */
	if (CACA_mono &&
		(CACA_pen != CACA_BORDER_CONST) && (CACA_pen != CACA_AXIS_CONST))
		pen = CACA_pen;

	if (pen != NUL)
		CACA_draw_line(CACA_cv, CACA_x, CACA_ymax - CACA_y, x, CACA_ymax - y, pen);
	else
		CACA_draw_thin_line(CACA_cv, CACA_x, CACA_ymax - CACA_y, x, CACA_ymax - y);

	if (pen != NUL && pen < ' ')
		FPRINTF((stderr, "Warning pen = %02x\n", (int) pen));

	if (CACA_keysample) {
		CACA_update_keybox(CACA_plotno, CACA_x, CACA_y);
		CACA_update_keybox(CACA_plotno, x, y);
	}

	CACA_x = x;
	CACA_y = y;
}


static void
CACA_update_keybox(unsigned plotno, unsigned x, unsigned y)
{
	CACA_RECT * bb;

	if (plotno == 0)
		return;
	if (plotno > CACA_maxkeyboxes) {
		int i;
		CACA_maxkeyboxes += 10;
		CACA_keyboxes = (CACA_RECT *) gp_realloc(CACA_keyboxes,
				CACA_maxkeyboxes * sizeof(CACA_RECT), "key boxes");
		for (i = plotno - 1; i < CACA_maxkeyboxes; i++) {
			CACA_keyboxes[i].left   = INT_MAX;
			CACA_keyboxes[i].right  = 0;
			CACA_keyboxes[i].bottom = INT_MAX;
			CACA_keyboxes[i].top    = 0;
		}
	}
	bb = CACA_keyboxes + plotno - 1;
	if (x < bb->left)   bb->left   = x;
	if (x > bb->right)  bb->right  = x;
	if (y < bb->bottom) bb->bottom = y;
	if (y > bb->top)    bb->top    = y;
}


void
CACA_layer(t_termlayer layer)
{
	int idx;

	switch (layer) {
		case TERM_LAYER_BEFORE_ZOOM:
			CACA_zoom_or_replot = TRUE;
			break;
		case TERM_LAYER_BEFORE_PLOT:
			CACA_plotno++;
			if (CACA_plotno >= CACA_maxhideplots) {
				CACA_maxhideplots += 10;
				CACA_hideplot = (TBOOLEAN *) gp_realloc(CACA_hideplot, CACA_maxhideplots * sizeof(TBOOLEAN), "hideplot");
				for (idx = CACA_plotno - 1; idx < CACA_maxhideplots; idx++)
					CACA_hideplot[idx] = FALSE;
			}
			if (CACA_plotno <= CACA_maxhideplots)
				CACA_skipplot = CACA_hideplot[CACA_plotno - 1];
			break;
		case TERM_LAYER_AFTER_PLOT:
			CACA_skipplot = FALSE;
			break;
		case TERM_LAYER_BEGIN_KEYSAMPLE:
			CACA_keysample = TRUE;
			break;
		case TERM_LAYER_END_KEYSAMPLE:
			CACA_keysample = FALSE;
			break;
		case TERM_LAYER_RESET:
			break;
		case TERM_LAYER_RESET_PLOTNO:
			CACA_plotno = 0;
			break;
		default:
			break;
	};
}


TERM_PUBLIC void
CACA_path(int p)
{
	if (CACA_cv == NULL)
		return;

	if (p == 0) { /* start new path */
		CACA_in_path = TRUE;
		CACA_polygon_points = 0;
		FPRINTF((stderr, "CACA: newpath\n"));
	} else if (p == 1) { /* close path */
		CACA_in_path = FALSE;
		FPRINTF((stderr, "CACA: closepath: %i points\n", CACA_polygon_points));
		if (CACA_polygon_points > 0) {
			/* Test for rectangles: */
			if (((CACA_charset == CACA_BLOCKS) || (CACA_charset == CACA_UNICODE)) &&
			     (CACA_polygon_points == 5) &&
			    ((CACA_path_x[0] == CACA_path_x[4]) && (CACA_path_y[0] == CACA_path_y[4])) &&
			   (((CACA_path_x[0] == CACA_path_x[4]) && (CACA_path_y[0] == CACA_path_y[4]) &&
			     (CACA_path_x[0] == CACA_path_x[1]) && (CACA_path_y[0] == CACA_path_y[3])) ||
			    ((CACA_path_x[0] == CACA_path_x[3]) && (CACA_path_y[0] == CACA_path_y[1]) &&
			     (CACA_path_x[2] == CACA_path_x[1]) && (CACA_path_y[2] == CACA_path_y[3])))) {
				/* skip empty boxes */
				if ((CACA_path_x[0] == CACA_path_x[2]) || (CACA_path_y[0] == CACA_path_y[2]))
					return;
				CACA_draw_cp437_box(CACA_cv,
									GPMIN(CACA_path_x[0], CACA_path_x[2]),
									GPMIN(CACA_path_y[0], CACA_path_y[2]),
									ABS(CACA_path_x[0] - CACA_path_x[2]) + 1,
									ABS(CACA_path_y[0] - CACA_path_y[2]) + 1);
			} else { /* Always use thin lines for ASCII. */
				CACA_draw_thin_polyline(
					CACA_cv, CACA_path_x, CACA_path_y, CACA_polygon_points - 1);
			}
		}
	}
}


static void
CACA_add_path_point(int x, int y)
{
	if (CACA_polygon_points >= CACA_maxpath) {
		CACA_maxpath += 10;
		CACA_path_x = (int *) gp_realloc(CACA_path_x, CACA_maxpath * sizeof(int), "path_x");
		CACA_path_y = (int *) gp_realloc(CACA_path_y, CACA_maxpath * sizeof(int), "path_y");
	}
	CACA_path_x[CACA_polygon_points] = x;
	CACA_path_y[CACA_polygon_points] = CACA_ymax - y;
	CACA_polygon_points++;
	FPRINTF((stderr, "CACA: new polygon point: %i %i\n", x, y));
}


struct angles {
	int angle;
	int xdiv;
	int ydiv;
};


/* Table assumes aspect ratio of characters of 16:10. */
static struct angles CACA_angles[] = {
	{ 0, 1, 1000},
	{28, 1, 3},
	{39, 1, 2},
	{58, 1, 1},
	{73, 2, 1},
	{78, 3, 1},
	{90, 1000, 1},
	{-1, 0, 0}
};


static void
CACA_init_angles(void)
{
	int i;
	int width = 6;
	int height = 10;

	if (CACA_dp != NULL) {
		height = caca_get_display_height(CACA_dp) / (CACA_ymax + CACA_status_height + 1);
		width  = caca_get_display_width(CACA_dp)  / (CACA_xmax + 1);
	}

	for (i = 0; CACA_angles[i].angle >= 0; i++) {
		CACA_angles[i].angle = (int) (0.5 + 180. / M_PI *
								atan2l(CACA_angles[i].xdiv * height,
									   CACA_angles[i].ydiv * width));
	}
}


static char *
CACA_encode_text(const char *str)
{
	char * text = (char * ) str;
	int i;

	if (text == NULL)
		return NULL;

	if (((encoding == S_ENC_DEFAULT) || (encoding == S_ENC_UTF8))
		&& (CACA_charset == CACA_ASCII)) {
		/* convert from utf8 to ascii */
		char const * s = str;
		size_t len = strlen_utf8(str);

		text = (char *) malloc(len + 1);
		i = 0;
		while ((*s != NUL) && (i < len)) {
			size_t bytes;
			uint32_t c = caca_utf8_to_utf32(s, &bytes);
			if (bytes == 0) {
				s++; /* skip illegal character */
			} else {
				text[i++] = caca_utf32_to_ascii(c);
				s += bytes;
			}
		}
		text[i] = NUL;
	} else if ((encoding == S_ENC_CP437) && (CACA_charset == CACA_ASCII)) {
		/* convert from cp437 to ascii */
		text = (char *) gp_alloc(strlen(str) + 1, "text");
		for (i = 0; i < strlen(str); i++) {
			uint32_t c = caca_cp437_to_utf32(str[i]);
			text[i] = caca_utf32_to_ascii(c);
		}
		text[strlen(str)] = NUL;
	} else if (encoding == S_ENC_CP437) {
		/* convert from cp437 to utf8 */
		char * s = text = (char *) gp_alloc(4 * strlen(str) + 1, "text");
		for (i = 0; i < strlen(str); i++) {
			uint32_t c = caca_cp437_to_utf32(str[i]);
			s += caca_utf32_to_utf8(s, c);
		}
		*s = NUL;
	}

	return text;
}


TERM_PUBLIC void
CACA_put_text(unsigned int x, unsigned int y, const char *str)
{
	/* Convert text encoding if necessary. */
	char * text = CACA_encode_text(str);
	int xpos, ypos;
	int xmax = x, ymax = y;

	if (CACA_cv == NULL)
		return;

	if (CACA_skipplot && !CACA_keysample)
		return;

	if (CACA_ang == 0) {
		/* Speed up the default case. */
		int len = CACA_put_str(CACA_cv, x, CACA_ymax - y, text);
#ifdef EAM_BOXED_TEXT
		if (CACA_boxedtext.boxed) {
			xpos = x; /* signed */
			ypos = y;
			if (CACA_boxedtext.box.left > xpos)
				CACA_boxedtext.box.left = xpos;
			if (CACA_boxedtext.box.right < (xpos + len))
				CACA_boxedtext.box.right = xpos + len;
			if (CACA_boxedtext.box.top < ypos)
				CACA_boxedtext.box.top = ypos;
			if (CACA_boxedtext.box.bottom > ypos)
				CACA_boxedtext.box.bottom = ypos;
			CACA_boxedtext.angle = 0;
		}
#endif
		if (CACA_keysample) {
			CACA_update_keybox(CACA_plotno, x, y);
			CACA_update_keybox(CACA_plotno, x + len, y);
		}
	} else {
		int quadrant;
		int angle = CACA_ang;
		int xsign = 1, ysign = 1;
		int i, n;

		/* Table covers only first quadrant. */
		quadrant = angle / 90;
		switch (quadrant) {
		case 0:
			/* nothing to do */
			break;
		case 1:
			angle = 90 - angle % 90;
			xsign = -1;
			break;
		case 2:
			angle %= 90;
			xsign = ysign = -1;
			break;
		case 3:
			angle = 90 - angle % 90;
			ysign = -1;
			break;
		}

		/* Find closest match in table to selected angle. */
		i = 0;
		while ((CACA_angles[i + 1].angle > 0) && (CACA_angles[i + 1].angle < angle)) i++;
		if ((angle - CACA_angles[i].angle) > (CACA_angles[i + 1].angle - angle)) i++;

		/* Print text */
		if (CACA_charset == CACA_ASCII)
			for (n = 0; n < strlen(text); n++) {
				caca_put_char(CACA_cv, xmax = (x + xsign * n / CACA_angles[i].xdiv),
								CACA_ymax - (ymax = (y + ysign * n / CACA_angles[i].ydiv)),
								text[n]);
			}
		else {
			int k = 0;
			/* Text is encoded in utf8 */
			for (n = k = 0; text[n] != 0; ) {
				size_t bytes;
				uint32_t c = caca_utf8_to_utf32(text + n, &bytes);
				if (bytes > 0) {
					n += bytes;
					CACA_put_transparent_char(CACA_cv, xmax = (x + xsign * k / CACA_angles[i].xdiv),
									CACA_ymax - (ymax = (y + ysign * k / CACA_angles[i].ydiv)), c);
					k += caca_utf32_is_fullwidth(c) ? 2 : 1;
				} else {
					n++; /* skip invalid character */
				}
			}
		}

#ifdef EAM_BOXED_TEXT
		if (CACA_boxedtext.boxed) {
			xpos = x; /* signed */
			ypos = y;
			if (xmax > x) {
				if (CACA_boxedtext.box.left > xpos)
					CACA_boxedtext.box.left = xpos;
				if (CACA_boxedtext.box.right < xmax)
					CACA_boxedtext.box.right = xmax;
			} else {
				if (CACA_boxedtext.box.left > xmax)
					CACA_boxedtext.box.left = xmax;
				if (CACA_boxedtext.box.right < xpos)
					CACA_boxedtext.box.right = xpos;
			}
			if (ymax > y) {
				if (CACA_boxedtext.box.top < ymax)
					CACA_boxedtext.box.top = ymax;
				if (CACA_boxedtext.box.bottom > ypos)
					CACA_boxedtext.box.bottom = ypos;
			} else {
				if (CACA_boxedtext.box.top < ypos)
					CACA_boxedtext.box.top = ypos;
				if (CACA_boxedtext.box.bottom > ymax)
					CACA_boxedtext.box.bottom = ymax;
			}
			CACA_boxedtext.angle = CACA_ang;
		}
#endif
		if (CACA_keysample) {
			CACA_update_keybox(CACA_plotno, x, y);
			CACA_update_keybox(CACA_plotno, xmax, ymax);
		}
	}

	if (text != str)
		free(text);
}


TERM_PUBLIC void
CACA_arrow(unsigned int sx, unsigned int sy, unsigned int ex, unsigned int ey, int head)
{
	if (CACA_cv == NULL)
		return;

	if (CACA_skipplot && !CACA_keysample)
		return;

	CACA_draw_thin_line(CACA_cv, sx, CACA_ymax - sy, ex, CACA_ymax - ey);
	if (head) {
		wchar_t const * heads;
		int dx = ex - sx;
		int dy = ey - sy;
		int idx = (int)(atan2(dy, dx) / M_PI * 4. + 8.5) % 8;
		switch (CACA_charset) {
			case CACA_ASCII:
				heads = L">+^+<+v+";
				break;
			case CACA_BLOCKS:
			default : {
				const wchar_t block_arrows[] = {
					0x2192, 0x2192, 0x2191, 0x2191, 0x2190, 0x2190, 0x2193, 0x2193
				}; /*  L"→→↑↑←←↓↓" */
				heads = block_arrows;
				break;
			}
#ifndef WIN32
			case CACA_UNICODE: {
				/* Windows: 45° arrows are not available in Lucida Console nor Consolas. */
 				const wchar_t unicode_arrows[] = {
					0x2192, 0x2197, 0x2191, 0x2196, 0x2190, 0x2199, 0x2193, 0x2198
				}; /*  L"→↗↑↖←↙↓↘" */
				heads = unicode_arrows; /* unicode simple arrows */
				break;
			}
#endif
		}
		CACA_put_transparent_char(CACA_cv, ex, CACA_ymax - ey, heads[idx]);
	}
}


TERM_PUBLIC int
CACA_text_angle(int ang)
{
	CACA_ang = ang;
	while (CACA_ang < 0)
		CACA_ang += 360;
	CACA_ang %= 360;
	return TRUE;
}


static wchar_t
CACA_fillstyle(int style)
{
	wchar_t fillchar = ' ';

	switch (style & 0x0f) {
		case FS_SOLID:
		case FS_TRANSPARENT_SOLID: {
			/* fill with intensity according to density */
			const wchar_t fillchars[] = {
				0x2588 /* full block */, 0x2593 /* dark */, 0x2592 /* medium */, 0x2591 /* light shade */,
				0x0020
			};
			const int patterns = sizeof(fillchars) / sizeof(wchar_t) - 1;
			int density = style >> 4;

			if (density < 0) density = 0;
			if (density > 100 ) density = 100;
			fillchar = fillchars[(density + 10) / (100 / patterns)];
			CACA_invertfill = TRUE;
			break;
		}
		case FS_PATTERN:
		case FS_TRANSPARENT_PATTERN: {
			/* fill with pattern according to fillpattern */
			const wchar_t ascii_fill[] = {
				' ', 'X', '\\', '/', '=', '<', '>'
			};
			const int ascii_patterns = sizeof(ascii_fill) / sizeof(wchar_t) - 1;
			const wchar_t block_fill[] = {
				' ', 'X', '\\', '/', '=', '<', '>',
				0x2580 /* upper half block */, 0x2584 /* lower half block */,
				0x258c /* left half block */, 0x2590 /* right half block */,
				0x2593 /* dark */, 0x2592 /* medium */, 0x2591 /* light shade */, 0x2588 /* full block */,
			};
			const int block_patterns = sizeof(block_fill) / sizeof(wchar_t) - 1;
			const wchar_t unicode_fill[] = {
				' ', 0x2573 /* diagonal cross */, 0x2588 /* full block */,
				0x2572 /* diagonal upper left to lower right */,
				0x2571 /* diagonal upper right to lower left */,
				0x2580 /* upper half block */, 0x2584 /* lower half block */,
				0x258c /* left half block */, 0x2590 /* right half block */,
				0x2593 /* dark */, 0x2592 /* medium */, 0x2591 /* light shade */, 0x2588 /* full block */,
			};
			const int unicode_patterns = sizeof(unicode_fill) / sizeof(wchar_t) -1;

			int pattern = style >> 4;
			switch (CACA_charset) {
				case CACA_ASCII:
					pattern %= ascii_patterns;
					fillchar = ascii_fill[pattern];
					break;
				case CACA_BLOCKS:
				default:
					pattern %= block_patterns;
					fillchar = block_fill[pattern];
					break;
				case CACA_UNICODE:
					pattern %= unicode_patterns;
					fillchar = unicode_fill[pattern];
					break;
					break;
			}
			CACA_invertfill = FALSE;
			break;
		}
		case FS_DEFAULT:
			fillchar = 0x2588; /* full block */
			CACA_invertfill = FALSE;
			break;
		case FS_EMPTY:
		default:
			/* fill with background color */
			fillchar = 0x2588; /* full block */
			CACA_invertfill = TRUE;
	}
	return fillchar;
}


TERM_PUBLIC void
CACA_fillbox(int style, unsigned int x, unsigned int y, unsigned int w, unsigned int h)
{
	wchar_t fillchar;
	uint32_t attr;

	if (CACA_cv == NULL)
		return;

	if (CACA_skipplot && !CACA_keysample)
		return;

	fillchar = CACA_fillstyle(style);

	attr = caca_get_attr(CACA_cv, -1, -1);
	if (CACA_invertfill) {
		/* Switch foreground/background color */
		caca_set_attr(CACA_cv,
					((attr & 0x0003fff0) << 14) |
					((attr & 0xfffc0000) >> 14) |
					 (attr & 0x0000000f));
	}

	if (h == 0) {
		caca_fill_box(CACA_cv, x, CACA_ymax - y, w, 1, fillchar);
	} else if ((w > 1) && (h > 1)) {
		/* Empirical fix to correctly fill boxes, see fillstyle.dem. */
		caca_fill_box(CACA_cv, x, CACA_ymax - y - h + 1, w, h - 1, fillchar);
	}

	caca_set_attr(CACA_cv, attr);

	if (CACA_keysample) {
		CACA_update_keybox(CACA_plotno, x, y);
		CACA_update_keybox(CACA_plotno, x + w, y + h);
	}
}

#ifdef USE_MOUSE

static int
CACA_translate_key(int key)
{
	const int caca_keys[] = {
		CACA_KEY_DELETE, CACA_KEY_UP,     CACA_KEY_DOWN, CACA_KEY_LEFT,
		CACA_KEY_RIGHT,  CACA_KEY_INSERT, CACA_KEY_HOME, CACA_KEY_END,
		CACA_KEY_PAGEUP, CACA_KEY_PAGEDOWN, /* editing */
		CACA_KEY_F1,  CACA_KEY_F2,  CACA_KEY_F3,  CACA_KEY_F4,
		CACA_KEY_F5,  CACA_KEY_F6,  CACA_KEY_F7,  CACA_KEY_F8,
		CACA_KEY_F9,  CACA_KEY_F10, CACA_KEY_F11, CACA_KEY_F12,
		CACA_KEY_F13, CACA_KEY_F14, CACA_KEY_F15,
		-1
	};
	const int gp_keys[] = {
		GP_Delete, GP_Up,     GP_Down, GP_Left,
		GP_Right,  GP_Insert, GP_Home, GP_End,
		GP_PageUp, GP_PageDown, /* editing */
		GP_F1,  GP_F2,  GP_F3,  GP_F4,
		GP_F5,  GP_F6,  GP_F7,  GP_F8,
		GP_F9,  GP_F10, GP_F11, GP_F12,
		-1, -1, -1, /* no equivalent gnuplot key definition */
		-1
	};
	int i;

	for (i = 0; caca_keys[i] != -1; i++) {
		if (key == caca_keys[i])
			return gp_keys[i];
	}
	return key;
}


static unsigned long
CACA_event_time(void)
{
#ifdef HAVE_SYS_TIME_H
	struct timeval tv;

	gettimeofday(&tv, NULL);
	return (tv.tv_sec) * 1000 + (tv.tv_usec) / 1000;
#elif defined (WIN32)
	FILETIME time;

	GetSystemTimeAsFileTime(&time);
	return time.dwLowDateTime / 1000;
#else
# error "caca.trm: No event time available"
#endif
}


TBOOLEAN
CACA_process_events(void)
{
	TBOOLEAN loop = TRUE;
	caca_event_t ev;
	const int event_mask =
		CACA_EVENT_KEY_PRESS | CACA_EVENT_RESIZE |  CACA_EVENT_QUIT |
		CACA_EVENT_MOUSE_MOTION | CACA_EVENT_MOUSE_PRESS | CACA_EVENT_MOUSE_RELEASE;
	static int mx = 0, my = 0; /* current mouse position */
	static unsigned long last_event_time = 0;

	if (CACA_dp != NULL) {
		if (caca_get_event(CACA_dp, event_mask, &ev, 100)) {
			switch (caca_get_event_type(&ev)) {
				case CACA_EVENT_MOUSE_MOTION : {
					mx = caca_get_event_mouse_x(&ev);
					my = CACA_ymax - caca_get_event_mouse_y(&ev);
					if ((CACA_mouse.x == mx) && (CACA_mouse.y == my))
						break; /* Suppress duplicate mouse motion events. */
					CACA_mouse.x = mx;
					CACA_mouse.y = my;
					if (CACA_zooming || CACA_ruler_line || (CACA_hypertext_list != NULL)) {
						CACA_zoom_to.x = mx;
						CACA_zoom_to.y = my;
						CACA_refresh();
					}
					process_event(GE_motion, mx, my, 0, 0, 0);
					/* FPRINTF((stderr, "CACA: mouse move: pos = %i, %i\n", mx, my)); */
					break;
				}
				case CACA_EVENT_MOUSE_PRESS : {
					int button = caca_get_event_mouse_button(&ev);
					if (button == 1) {
						int i;

						for (i = 0; (i < CACA_plotno) && (i < CACA_maxkeyboxes) && (i < CACA_maxhideplots); i++) {
							/* Is this a click in a key box? */
							if ((CACA_keyboxes[i].left != INT_MAX) &&
								(CACA_mouse.x >= CACA_keyboxes[i].left) &&
								(CACA_mouse.x <= CACA_keyboxes[i].right) &&
								(CACA_mouse.y <= CACA_keyboxes[i].top) &&
								(CACA_mouse.y >= CACA_keyboxes[i].bottom)) {
								CACA_hideplot[i] = ! CACA_hideplot[i];
								FPRINTF((stderr, "CACA: hide plot #%i: %i\n", i, CACA_hideplot[i]));
								/* If something changed, replot and do not pass this event on to the core. */
								CACA_zoom_or_replot = TRUE;
								process_event(GE_replot, 0, 0, 0, 0, 0);
								break;
							}
						}
					}
					process_event(GE_buttonpress, mx, my, button, 0, 0);
					FPRINTF((stderr, "CACA: mouse press: button = %i, pos = %i, %i\n", button, mx, my));
					break;
				}
				case CACA_EVENT_MOUSE_RELEASE : {
					unsigned long timestamp;
					int button = caca_get_event_mouse_button(&ev);
					unsigned long event_time = CACA_event_time();
					timestamp = event_time - last_event_time;
					last_event_time = event_time;
					process_event(GE_buttonrelease, mx, my, button, timestamp, 0);
					FPRINTF((stderr, "CACA: mouse release: button = %i, pos = %i, %i time = %lu\n", button, mx, my, timestamp));
					break;
				}
				case CACA_EVENT_KEY_PRESS: {
					int key = CACA_translate_key(caca_get_event_key_ch(&ev));
					switch (key) {
						case 'q':
						case 'Q':
							/* preserve canvas, close window */
							CACA_close_display();
							loop = FALSE;
							break;
						case ' ':
							if (CACA_windowed) {
#ifdef DISABLE_SPACE_RAISES_CONSOLE
								process_event(GE_keypress, mx, my, key, 0, 0);
#else
# ifdef WIN32
								/* raise console */
								WinRaiseConsole();
# else
								/* FIXME: raising terminal window not implemented */
								/* gp_raise_console(); */
								process_event(GE_keypress, mx, my, key, 0, 0);
# endif
#endif
							} else {
								/* preserve canvas, close window */
								CACA_close_display();
								loop = FALSE;
							}
							break;
						case GP_Return:
						case GP_Escape:
						case GP_BackSpace:
						case GP_Delete:
							if (CACA_windowed)
								process_event(GE_keypress, mx, my, key, 0, 0);
							else {
								CACA_close_display();
								loop = FALSE;
							}
							break;
						case CACA_KEY_CTRL_C:
							/* Since we receive this key code, it is likely that the libcaca backend
							   driver installed its own SIGINT handler.  Make sure that we call
							   gnuplot's signal handler after cleaning up. */
							fprintf(stderr, "CTRL-C pressed. Forwarding to SIGINT handler.\n");
							if (!CACA_windowed)
								CACA_close_display();
							interrupt_setup();
							raise(SIGINT);
							break;
						case -1:
							/* No equivalent special key available */
							break;
						default:
							process_event(GE_keypress, mx, my, key, 0, 0);
					}
					break;
				}
				case CACA_EVENT_RESIZE: {
					term->xmax = CACA_xmax = caca_get_event_resize_width(&ev) - 1;
					term->ymax = CACA_ymax = caca_get_event_resize_height(&ev) - 1 - CACA_status_height;
					CACA_update_options();
					CACA_zoom_or_replot = TRUE;
					process_event(GE_replot, 0, 0, 0, 0, 0);
					CACA_refresh();
					FPRINTF((stderr, "CACA: resize event: %i x %i\n", CACA_xmax  + 1, CACA_ymax + 1 + CACA_status_height));
					break;
				}
				case CACA_EVENT_QUIT:
					loop = FALSE;
					break;
				case CACA_EVENT_NONE:
				default:
					break;
			}
		}
	} else {
		/* No display to process events for. Get out of here. */
		loop = FALSE;
	}

#ifdef WIN32
	/* Handle Windows message queue events. */
	WinMessageLoop();
#endif

	return loop;
}


TBOOLEAN
CACA_window_opened()
{
	return (CACA_dp != NULL);
}


static void
CACA_discard_events(void)
{
	if (CACA_dp != NULL)
		while (caca_get_event(CACA_dp, CACA_EVENT_ANY, NULL, 50) != 0);
}


TERM_PUBLIC int
CACA_waitforinput(int options)
{
	TBOOLEAN loop = TRUE;

    /* Not implemented: hotkeys during non-interactive input */
    if (options == TERM_ONLY_CHECK_MOUSING)
		return NUL;

	/* Redundant check since normally term->waitforinput should be NULL in
	  this case. */
	if (!CACA_windowed)
		return getchar();

	while (loop && (CACA_dp != NULL)) {
#ifdef WIN32
		HANDLE h;
		DWORD res;

# ifndef WGP_CONSOLE
		/* Take care of the caret. */
		TextStartEditing(&textwin);
# endif
		/* Non-blocking windows event loop: */
		h = GetStdHandle(STD_INPUT_HANDLE);
		res = WAIT_OBJECT_0 + 1;
		if (h != NULL)
			res = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_ALLINPUT);
		else
			WaitMessage();
		if (res == WAIT_OBJECT_0)
			loop = CACA_process_events();
		else if (res == WAIT_OBJECT_0 + 1)
			WinMessageLoop();
# ifndef WGP_CONSOLE
		/* Hide caret again. */
		TextStopEditing(&textwin);
# endif
#else
		loop = CACA_process_events();
#endif
		if (loop && gp_kbhit())
			return getchar();
	}

	/* We get here only if the window was closed, e.g. via pressing the 'q' key. */
	if (CACA_windowed)
		return getchar();

	return EOF;
}


static void
CACA_print_status(void)
{
	if ((CACA_dp != NULL) && (CACA_screen_cv != NULL)) {
		uint32_t attr = caca_get_attr(CACA_screen_cv, -1, -1);
		if (!CACA_inverted)
			caca_set_color_ansi(CACA_screen_cv, CACA_WHITE, CACA_BLUE);
		else
			caca_set_color_ansi(CACA_screen_cv, CACA_BLUE, CACA_WHITE);
		caca_draw_line(CACA_screen_cv, 0, CACA_ymax + 1, CACA_xmax, CACA_ymax + 1, ' ');
		if (CACA_status_text != NULL)
			caca_put_str(CACA_screen_cv, 2, CACA_ymax + 1, CACA_status_text);
		caca_set_attr(CACA_screen_cv, attr);
	}
}


TERM_PUBLIC void
CACA_put_tmptext(int i, const char str[])
{
	if ((CACA_dp == NULL) || CACA_exportonly)
		return;

	switch (i) {
		case 0: /* status text */
			free(CACA_status_text);
			if ((str != NULL) && (strlen(str) > 0))
				CACA_status_text = strdup(str);
			else
				CACA_status_text = NULL;
			CACA_print_status();
			if (CACA_dp != NULL)
				caca_refresh_display(CACA_dp);
			break;
		case 1: /* zooming from text */
			free(CACA_zoom_from_text);
			if ((str != NULL) && (strlen(str) > 0))
				CACA_zoom_from_text = strdup(str);
			else
				CACA_zoom_from_text = NULL;
			CACA_refresh();
			break;
		case 2: /* zooming to text */
			free(CACA_zoom_to_text);
			if ((str != NULL) && (strlen(str) > 0))
				CACA_zoom_to_text = strdup(str);
			else
				CACA_zoom_to_text = NULL;
			CACA_refresh();
			break;
	}
}


TERM_PUBLIC void
CACA_set_ruler(int x, int y)
{
	if ((CACA_dp == NULL) || CACA_exportonly)
		return;

	CACA_ruler.x = x;
	CACA_ruler.y = y;
	CACA_refresh();
}


TERM_PUBLIC void
CACA_set_cursor(int c, int x, int y)
{
	if ((CACA_dp == NULL) || CACA_exportonly)
		return;

	switch (c) {
		case -1: /* start zooming */
			CACA_zooming = TRUE;
			CACA_zoom_from.x = CACA_zoom_to.x = x;
			CACA_zoom_from.y = CACA_zoom_to.y = y;
			break;
		case -2: /* warp mouse cursor */
			/* function unavailable */
			break;
		case -3: /* draw line to ruler */
			CACA_ruler_line = TRUE;
			break;
		case -4: /* erase line to ruler */
			CACA_ruler_line = FALSE;
			break;
		case  0: /* standard (cross-hair) cursor */
		case  1: /* rotation cursor */
		case  2: /* scaling cursor */
		case  3: /* zooming cursor */
			/* cannot change the cursor shape */
			caca_set_cursor(CACA_dp, 1);
			break;
	}
	if (c >= 0)	/* stop zooming */
		CACA_zooming = FALSE;
	CACA_refresh();
}

#endif /* USE_MOUSE */


static int
float_compare(const void * elem1, const void * elem2)
{
	int val = *(float *)elem1 - *(float *)elem2;
	return (0 < val) - (val < 0);
}


TERM_PUBLIC void
CACA_filled_polygon(int points, gpiPoint *corners)
{
	wchar_t fillchar;
	uint32_t attr;

	if (CACA_cv == NULL)
		return;

	if (CACA_skipplot && !CACA_keysample)
		return;

	/* Eliminate duplicate polygon points. */
	if ((corners[0].x == corners[points - 1].x) && (corners[0].y == corners[points - 1].y))
		points--;

	/* Need at least three remaining points */
	if (points < 3)
	    return;

	fillchar = CACA_fillstyle(corners->style);
	attr = caca_get_attr(CACA_cv, -1, -1);
	if (CACA_invertfill) {
		/* Switch foreground/background color */
		caca_set_attr(CACA_cv,
					((attr & 0x0003fff0) << 14) |
					((attr & 0xfffc0000) >> 14) |
					 (attr & 0x0000000f));
	}

	if ((points == 4) &&
		(((corners[0].x == corners[3].x) && (corners[0].y == corners[1].y) &&
		     (corners[2].x == corners[1].x) && (corners[2].y == corners[3].y)) ||
		    ((corners[0].x == corners[1].x) && (corners[0].y == corners[3].y) &&
		     (corners[2].x == corners[3].x) && (corners[2].y == corners[1].y)))) {
		/* The polygon describes a rectangle. Thus we use boxfill, which is more efficient. */
		caca_fill_box(CACA_cv,
					GPMIN(corners[0].x, corners[2].x), CACA_ymax - GPMAX(corners[0].y, corners[2].y),
					abs(corners[2].x - corners[0].x) + 1, abs(corners[2].y - corners[0].y) + 1,
					fillchar);
	} else { /* general case */
		/* ----------------------------------------------------------------
		 * Derived from
		 *  public-domain code by Darel Rex Finley, 2007
		 *  http://alienryderflex.com/polygon_fill/
		 * ---------------------------------------------------------------- */
		int nodes;
		float * nodeX;
		int pixelY;
		int i, j;
		int ymin = CACA_ymax, ymax = 0;
		int xmin = CACA_xmax, xmax = 0;

		FPRINTF((stderr, "CACA: fill polygon: n = %i\n", points));
		/* Find bounding box */
		for (i = 0; i < points; i++) {
			if (corners[i].x < xmin) xmin = corners[i].x;
			if (corners[i].x > xmax) xmax = corners[i].x;
			if (corners[i].y < ymin) ymin = corners[i].y;
			if (corners[i].y > ymax) ymax = corners[i].y;
		}

		/* Dynamically allocate node list. */
		nodeX = (float *) gp_alloc(sizeof(* nodeX) * points, "nodeX");

		/* Loop through the rows of the image. */
		for (pixelY = ymin; pixelY <= ymax + 1; pixelY++) {

			/* Build a sorted list of nodes. */
			nodes = 0;
			j = points - 1;
			for (i = 0; i < points; i++) {
				if (((corners[i].y < pixelY) && (corners[j].y >= pixelY)) ||
					((corners[j].y < pixelY) && (corners[i].y >= pixelY))) {
					nodeX[nodes++] = (corners[i].x +
											+ (double) (pixelY - corners[i].y)
											/ (double) (corners[j].y - corners[i].y)
											* (double) (corners[j].x - corners[i].x));
				}
				j = i;
			}
			qsort(nodeX, nodes, sizeof(float), float_compare);
#ifdef DEBUG
			for (i = j = 0; i < nodes - 1; i++)
				if (nodeX[i] > nodeX[i + 1]) j = 1;
			if (j) FPRINTF((stderr, "Polygon fill error: node points are unsorted!\n"));
#endif

			/* Fill the pixels between node pairs. */
			for (i = 0; i < nodes; i += 2) {
				if (nodeX[i] > xmax)
					break;
				if (nodeX[i + 1] >= 0) {
					/* TODO: Are these checks ever required? */
					if (nodeX[i] < xmin)
						nodeX[i] = xmin;
					if (nodeX[i + 1] > xmax)
						nodeX[i + 1] = xmax;
					/* skip lines with zero length */
					if (nodeX[i + 1] - nodeX[i] < 0.5)
						continue;
					caca_draw_line(CACA_cv,
									(int)(nodeX[i] + 0.5), CACA_ymax - pixelY,
									(int)(nodeX[i + 1]), CACA_ymax - pixelY,
									fillchar);
				}
			}
		}

		/* cleanup */
		free(nodeX);
		/* ---------------------------------------------------------------- */
	}

	/* restore color */
	caca_set_attr(CACA_cv, attr);
}


TERM_PUBLIC void
CACA_image(unsigned int M, unsigned int N, coordval *image,
	  gpiPoint *corner, t_imagecolor color_mode)
{
	caca_dither_t * dither;
	int bpp;
	int rmask, bmask, gmask, amask;
	uint8_t * data;
	int n;

	if (CACA_cv == NULL)
		return;

	switch (color_mode) {
		case IC_PALETTE:
			FPRINTF((stderr, "CACA_image: palette\n"));
			bpp = 8;
			rmask = gmask = bmask = amask = 0;
			break;
		case IC_RGB:
			FPRINTF((stderr, "CACA_image: rgb\n"));
			bpp = 24;
			rmask = 0x0000ff;
			gmask = 0x00ff00;
			bmask = 0xff0000;
			amask = 0x000000;
			break;
		case IC_RGBA:
			/* NOTE: Sadly, libcaca does not really support the alpha channel. */
			FPRINTF((stderr, "CACA_image: rgba\n"));
			bpp = 32;
			rmask = 0x000000ff;
			gmask = 0x0000ff00;
			bmask = 0x00ff0000;
			amask = 0xff000000;
			break;
		default: /* unknown */
			FPRINTF((stderr, "CACA_image: unknown\n"));
			return;
	}

	/* convert image to integer */
	data = (uint8_t *) gp_alloc(M * N * bpp / 8, "image data");
	for (n = 0; n < M * N * bpp / 8; n++)
		data[n] = 255 * image[n];

	/* create dither object */
	dither = caca_create_dither(bpp, M, N, M * bpp / 8 /* pitch */, rmask, gmask, bmask, amask);

	caca_set_dither_algorithm(dither, "ordered8");
	switch (CACA_charset) {
		case CACA_ASCII:
			caca_set_dither_charset(dither, "ascii");  /* default */
			break;
		case CACA_BLOCKS:
			caca_set_dither_charset(dither, "blocks"); /* Unicode */
			break;
		case CACA_UNICODE:
			caca_set_dither_charset(dither, "shades"); /* CP437 and Unicode */
			break;
	}

	if (CACA_mono)
		caca_set_dither_color(dither, "fullgray");
	else
		/* FIXME: Some terminal/driver combinations might require full8 instead
		   of full16 in order to avoid blinking. */
		caca_set_dither_color(dither, "full16"); /* default */

	/* create palette data */
	if (bpp == 8) {
		uint32_t red[256], green[256], blue[256], alpha[256];
		int i;
		rgb255_color rgb255;

		for (i = 0; i < 256; i++) {
			rgb255maxcolors_from_gray(i / 255.0, &rgb255);
			red[i]   = rgb255.r << 4;
			green[i] = rgb255.g << 4;
			blue[i]  = rgb255.b << 4;
			alpha[i] = 0;
		}
		caca_set_dither_palette(dither, red, green, blue, alpha);
	}

	caca_dither_bitmap(CACA_cv,
						corner[0].x, CACA_ymax - corner[0].y,
						corner[1].x - corner[0].x, abs(corner[0].y - corner[1].y),
						dither, data);
	free(data);
	caca_free_dither(dither);
}


void
CACA_hypertext(int type, const char *text)
{
	CACA_HYPERTEXT * hypertext = CACA_hypertext_list;

	if (type != TERM_HYPERTEXT_TOOLTIP)
		return;

	if (hypertext == NULL) {
		hypertext = CACA_hypertext_list = gp_alloc(sizeof(CACA_HYPERTEXT), "caca hypertext");
	} else {
		while (hypertext->next != NULL)
			hypertext = hypertext->next;
		hypertext->next = gp_alloc(sizeof(CACA_HYPERTEXT), "caca hypertext");
		hypertext = hypertext->next;
	}
	hypertext->loc.x = -1;
	hypertext->loc.y = -1;
	hypertext->next = NULL;
	hypertext->text = strdup(text);
	CACA_last_hypertext = hypertext;
}


#ifdef EAM_BOXED_TEXT
void
CACA_boxed_text(unsigned int x, unsigned int y, int option)
{
	switch (option) {
	case TEXTBOX_INIT:
		/* Initialise bounding box. */
		CACA_boxedtext.box.left   = INT_MAX;
		CACA_boxedtext.box.right  = 0;
		CACA_boxedtext.box.bottom = INT_MAX;
		CACA_boxedtext.box.top    = 0;
		CACA_boxedtext.margin.x = CACA_boxedtext.margin.y = 1;
		CACA_boxedtext.angle = CACA_ang;
		CACA_boxedtext.boxed = TRUE;
		break;
	case TEXTBOX_OUTLINE:
	case TEXTBOX_BACKGROUNDFILL: {
		int dx = CACA_boxedtext.margin.x;
		int dy = CACA_boxedtext.margin.y;

		/* Handle vertical and horizontal text boxes. */
		if ((CACA_boxedtext.angle % 90) == 0) {
			CACA_RECT rect;

			rect.left   = + CACA_boxedtext.box.left   - dx;
			rect.right  = + CACA_boxedtext.box.right  + dx;
			rect.top    = + CACA_boxedtext.box.top    + dy;
			rect.bottom = + CACA_boxedtext.box.bottom - dy;

			caca_set_color_ansi(CACA_cv,
				!CACA_inverted ? CACA_BLACK : CACA_WHITE, CACA_background);

			if (option == TEXTBOX_OUTLINE)
				/* draw rectangle */
				if ((CACA_charset == CACA_BLOCKS) || (CACA_charset == CACA_UNICODE))
					CACA_draw_cp437_box(CACA_cv,
						rect.left, CACA_ymax - rect.top,
						rect.right - rect.left + 1, rect.top - rect.bottom + 1);
				else
					CACA_draw_thin_box(CACA_cv,
						rect.left, CACA_ymax - rect.top,
						rect.right - rect.left + 1, rect.top - rect.bottom + 1);
			else
				/* Fill bounding box with background color. */
				caca_fill_box(CACA_cv,
					rect.left, CACA_ymax - rect.top,
					rect.right - rect.left + 1, rect.top - rect.bottom + 1,
					L' ');

			/* Stop updating the bounding box. */
			CACA_boxedtext.boxed = FALSE;
		}
		break;
	}
	case TEXTBOX_MARGINS:
		/* Ignore scaling of margins; always use one character cell. */
		CACA_boxedtext.margin.x = 1;
		CACA_boxedtext.margin.y = 1;
		break;
	}
}
#endif


TERM_PUBLIC void
CACA_modify_plots(unsigned int operations, int plotno)
{
	int i;
	TBOOLEAN changed = FALSE;

	for (i = 0; (i < CACA_plotno) && (i < CACA_maxkeyboxes) && (i < CACA_maxhideplots); i++) {

		if (plotno >= 0 && i != plotno)
			continue;

		switch (operations) {
		case MODPLOTS_INVERT_VISIBILITIES:
			CACA_hideplot[i] = ! CACA_hideplot[i];
			changed = TRUE;
			break;
		case MODPLOTS_SET_VISIBLE:
			if (CACA_hideplot[i]) {
				changed = TRUE;
				CACA_hideplot[i] = FALSE;
			}
			break;
		case MODPLOTS_SET_INVISIBLE:
			if (!CACA_hideplot[i]) {
				changed = TRUE;
				CACA_hideplot[i] = TRUE;
			}
			break;
		}
	}

	if (changed) {
		/* Replot only if something changed. */
		CACA_zoom_or_replot = TRUE;
		process_event(GE_replot, 0, 0, 0, 0, 0);
	}
}


/* -------------------------------------------------------------------------
   The "enhanced text" code below is a modified copy of the dumb terminal's
   code written by Ethan Merritt. See dumb.trm for Copyright on this section.
   ------------------------------------------------------------------------- */

static TBOOLEAN CACA_enhanced_opened_string;
static TBOOLEAN CACA_enhanced_show = TRUE;
static int CACA_enhanced_overprint = 0;
static TBOOLEAN CACA_enhanced_widthflag = TRUE;
static unsigned int CACA_enhanced_xsave, CACA_enhanced_ysave;
static double CACA_enhanced_base;


TERM_PUBLIC void
CACA_enhanced_open(
    char *fontname,
    double fontsize, double base,
    TBOOLEAN widthflag, TBOOLEAN showflag,
    int overprint)
{
	/* There are two special cases:
	 * overprint = 3 means save current position
	 * overprint = 4 means restore saved position
	 */
	if (overprint == 3) {
		CACA_enhanced_xsave = CACA_x;
		CACA_enhanced_ysave = CACA_y;
		return;
	} else if (overprint == 4) {
		CACA_x = CACA_enhanced_xsave;
		CACA_y = CACA_enhanced_ysave;
		return;
	}

	if (!CACA_enhanced_opened_string) {
		CACA_enhanced_opened_string = TRUE;
		/* Start new text fragment */
			enhanced_cur_text = &enhanced_text[0];
		/* Scale fractional font height to vertical units of display */
			CACA_enhanced_base = base * 2;
		/* Keep track of whether we are supposed to show this string */
			CACA_enhanced_show = showflag;
		/* 0/1/2  no overprint / 1st pass / 2nd pass */
			CACA_enhanced_overprint = overprint;
		/* widthflag FALSE means do not update text position after printing */
			CACA_enhanced_widthflag = widthflag;
		/* Many drivers will need to do something about font selection here */
		/* but this driver cannot do anything about it. */
	}
}


TERM_PUBLIC void
CACA_enhanced_flush()
{
	char *str = enhanced_text;	/* The fragment to print */
	int len;

	if (!CACA_enhanced_opened_string)
		return;

	*enhanced_cur_text = NUL;
	if (CACA_charset == CACA_ASCII)
		len = strlen(str);
	else
		len = strlen_utf8(str);

	/* print the string fragment, perhaps invisibly */
	/* NB: base expresses offset from current y pos */
	if (CACA_enhanced_show) {
		CACA_put_str(CACA_cv, CACA_x, CACA_ymax - (int)(CACA_y + CACA_enhanced_base), str);
#ifdef EAM_BOXED_TEXT
		if (CACA_boxedtext.boxed) {
			if (CACA_boxedtext.box.left > CACA_x)
				CACA_boxedtext.box.left = CACA_x;
			if (CACA_boxedtext.box.right < (CACA_x + len))
				CACA_boxedtext.box.right = CACA_x + len;
			if (CACA_boxedtext.box.top < (int)(CACA_y + CACA_enhanced_base))
				CACA_boxedtext.box.top = (int)(CACA_y + CACA_enhanced_base);
			if (CACA_boxedtext.box.bottom > (int)(CACA_y + CACA_enhanced_base))
				CACA_boxedtext.box.bottom = (int)(CACA_y + CACA_enhanced_base);
			CACA_boxedtext.angle = 0;
		}
		if (CACA_keysample) {
			CACA_update_keybox(CACA_plotno, CACA_x, CACA_y + CACA_enhanced_base);
			CACA_update_keybox(CACA_plotno, CACA_x + len, CACA_y + CACA_enhanced_base);
		}
#endif
	}

	if (!CACA_enhanced_widthflag)
		/* don't update position */
		;
	else if (CACA_enhanced_overprint == 1)
		/* First pass of overprint, leave position in center of fragment */
		CACA_x += len / 2;
	else
		/* Normal case is to update position to end of fragment */
		CACA_x += len;

	CACA_enhanced_opened_string = FALSE;
}


TERM_PUBLIC void
CACA_enhanced_put_text(unsigned int x, unsigned int y, const char *str)
{
	if (CACA_cv == NULL)
		return;

	if (CACA_skipplot && !CACA_keysample)
		return;

	/* If no enhanced text processing is needed, we can use the plain  */
	/* vanilla put_text() routine instead of this fancy recursive one. */
	if (ignore_enhanced_text || !strpbrk(str, "{}^_@&~")) {
		CACA_put_text(x, y, str);
		return;
	}

	/* Set up global variables needed by enhanced_recursion() */
	enhanced_fontscale = 1.0;
	CACA_enhanced_opened_string = FALSE;
	strncpy(enhanced_escape_format, "%c", sizeof(enhanced_escape_format));

	CACA_x = x;
	CACA_y = y;

	/* Set the recursion going. We say to keep going until a
	 * closing brace, but we don't really expect to find one.
	 * If the return value is not the nul-terminator of the
	 * string, that can only mean that we did find an unmatched
	 * closing brace in the string. We increment past it (else
	 * we get stuck in an infinite loop) and try again.
	 */
	while (*(str = enhanced_recursion((char *)str, TRUE,
				"" /* font */, 1 /* size */,
				0.0, TRUE, TRUE, 0))) {
		(term->enhanced_flush)();

		/* I think we can only get here if *str == '}' */
		enh_err_check(str);

		if (!*++str)
			break; /* end of string */

		/* else carry on and process the rest of the string */
	}
}

/* -------------------------------------------------------------------------
   End of "enhanced text" code. See comment above.
   ------------------------------------------------------------------------- */


/* -------------------------------------------------------------------------
   The following libcaca helper functions are mostly derived from code by Sam Hocevar
   found in libcaca files attr.c, line.c, string.c, and box.c. The files include the
   following notice:

 *  libcaca       Colour ASCII-Art library
 *  Copyright (c) 2002-2012 Sam Hocevar <sam@hocevar.net>
 *                All Rights Reserved
 *
 *  This library is free software. It comes without any warranty, to
 *  the extent permitted by applicable law. You can redistribute it
 *  and/or modify it under the terms of the Do What The Fuck You Want
 *  To Public License, Version 2, as published by Sam Hocevar. See
 *  http://sam.zoy.org/wtfpl/COPYING for more details.
   ------------------------------------------------------------------------- */

struct line
{
    int x1, y1;
    int x2, y2;
};


static void
CACA_put_transparent_char(caca_canvas_t *cv, int x, int y, uint32_t c)
{
	uint32_t attr_bg = caca_get_attr(cv, x, y);
	uint32_t attr_fg = caca_get_attr(cv, -1, -1);
	uint32_t k;

	/* The following code "merges" box drawing characters */
	switch (c) {
		case BOX_VERT: /* vertical line */
			k = caca_get_char(cv, x, y);
			switch (k) {
				case BOX_HOR:
				case BOX_UP_HOR:
				case BOX_DOWN_HOR:
				case BOX_VERT_HOR:
					c = BOX_VERT_HOR;
					break;
				case BOX_UP_LEFT:
				case BOX_DOWN_LEFT:
				case BOX_VERT_LEFT:
					c = BOX_VERT_LEFT;
					break;
				case BOX_UP_RIGHT:
				case BOX_DOWN_RIGHT:
				case BOX_VERT_RIGHT:
					c = BOX_VERT_RIGHT;
					break;
			}
			break;
		case BOX_HOR: /* horizontal line */
			k = caca_get_char(cv, x, y);
			switch (k) {
				case BOX_VERT:
				case BOX_VERT_LEFT:
				case BOX_VERT_RIGHT:
				case BOX_VERT_HOR:
					c = BOX_VERT_HOR;
					break;
				case BOX_UP_LEFT:
				case BOX_UP_RIGHT:
				case BOX_UP_HOR:
					c = BOX_UP_HOR;
					break;
				case BOX_DOWN_LEFT:
				case BOX_DOWN_RIGHT:
				case BOX_DOWN_HOR:
					c = BOX_DOWN_HOR;
					break;
			}
			break;
		case BOX_DOWN_RIGHT: /* upper left corner */
			k = caca_get_char(cv, x, y);
			switch (k) {
				case BOX_UP_RIGHT:
				case BOX_VERT:
				case BOX_VERT_RIGHT:
					c = BOX_VERT_RIGHT;
					break;
				case BOX_DOWN_LEFT:
				case BOX_HOR:
				case BOX_DOWN_HOR:
					c = BOX_DOWN_HOR;
					break;
				case BOX_UP_LEFT:
				case BOX_UP_HOR:
				case BOX_VERT_LEFT:
				case BOX_VERT_HOR:
					c = BOX_VERT_HOR;
					break;
			}
			break;
		case BOX_DOWN_LEFT: /* upper right corner */
			k = caca_get_char(cv, x, y);
			switch (k) {
				case BOX_UP_LEFT:
				case BOX_VERT:
				case BOX_VERT_LEFT:
					c = BOX_VERT_LEFT;
					break;
				case BOX_DOWN_RIGHT:
				case BOX_HOR:
				case BOX_DOWN_HOR:
					c = BOX_DOWN_HOR;
					break;
				case BOX_UP_RIGHT:
				case BOX_VERT_RIGHT:
				case BOX_UP_HOR:
				case BOX_VERT_HOR:
					c = BOX_VERT_HOR;
					break;
			}
			break;
		case BOX_UP_RIGHT: /* lower left corner */
			k = caca_get_char(cv, x, y);
			switch (k) {
				case BOX_DOWN_RIGHT:
				case BOX_VERT:
				case BOX_VERT_RIGHT:
					c = BOX_VERT_RIGHT;
					break;
				case BOX_UP_LEFT:
				case BOX_HOR:
				case BOX_UP_HOR:
					c = BOX_UP_HOR;
					break;
				case BOX_DOWN_LEFT:
				case BOX_DOWN_HOR:
				case BOX_VERT_LEFT:
				case BOX_VERT_HOR:
					c = BOX_VERT_HOR;
					break;
			}
			break;
		case BOX_UP_LEFT: /* lower right corner */
			k = caca_get_char(cv, x, y);
			switch (k) {
				case BOX_DOWN_LEFT:
				case BOX_VERT:
				case BOX_VERT_LEFT:
					c = BOX_VERT_LEFT;
					break;
				case BOX_UP_RIGHT:
				case BOX_HOR:
				case BOX_UP_HOR:
					c = BOX_UP_HOR;
					break;
				case BOX_DOWN_RIGHT:
				case BOX_VERT_RIGHT:
				case BOX_DOWN_HOR:
				case BOX_VERT_HOR:
					c = BOX_VERT_HOR;
					break;
			}
			break;
	}

	caca_set_attr(cv,
				(attr_fg & 0x0003ffff) | /* new foreground color (18 bits) */
				(attr_bg & 0xfffc0000)); /* keep current background color (14 bit) */
	caca_put_char(cv, x, y, c);
	caca_set_attr(cv, attr_fg);
}


static int
CACA_put_str(caca_canvas_t *cv, int x, int y, char const *s)
{
    size_t rd;
    int len = 0;

    if (y < 0 || y >= (int)CACA_ymax || x > (int)CACA_xmax)
    {
        while (*s)
        {
            len += caca_utf32_is_fullwidth(caca_utf8_to_utf32(s, &rd)) ? 2 : 1;
            s += rd ? rd : 1;
        }
        return len;
    }

    while (*s)
    {
        uint32_t ch = caca_utf8_to_utf32(s, &rd);

        if (x + len >= -1 && x + len < (int)CACA_xmax)
            CACA_put_transparent_char(cv, x + len, y, ch);

        len += caca_utf32_is_fullwidth(ch) ? 2 : 1;
        s += rd ? rd : 1;
    }

    return len;
}


/* Solid line drawing function, using Bresenham's mid-point line
 * scan-conversion algorithm. */
static void
CACA_draw_line(caca_canvas_t *cv, int x1, int y1, int x2, int y2, uint32_t ch)
{
    int dx, dy;
    int xinc, yinc;
	TBOOLEAN first = TRUE;

    dx = abs(x2 - x1);
    dy = abs(y2 - y1);

    xinc = (x1 > x2) ? -1 : 1;
    yinc = (y1 > y2) ? -1 : 1;

    if(dx >= dy)
    {
        int dpr = dy << 1;
        int dpru = dpr - (dx << 1);
        int delta = dpr - dx;

        for(; dx>=0; dx--)
        {
#if 0
            /* Try to merge tic marks and border. Mostly useless since border is normally drawn after the tic marks. */
            if (first && (xinc > 0) && (ch == BOX_HOR) && (caca_get_char(cv, x1, y1) == BOX_VERT))
                CACA_put_transparent_char(cv, x1, y1, BOX_VERT_RIGHT); /* left inward or right outward tics */
            else if (first && (xinc < 0) && (ch == BOX_HOR) && (caca_get_char(cv, x1, y1) == BOX_VERT))
                CACA_put_transparent_char(cv, x1, y1, BOX_VERT_LEFT); /* right inward or left outward tics */
            else
#endif
                CACA_put_transparent_char(cv, x1, y1, ch);
            first = FALSE;
            if(delta > 0)
            {
                x1 += xinc;
                y1 += yinc;
                delta += dpru;
            }
            else
            {
                x1 += xinc;
                delta += dpr;
            }
        }
    }
    else
    {
        int dpr = dx << 1;
        int dpru = dpr - (dy << 1);
        int delta = dpr - dy;

        for(; dy >= 0; dy--)
        {
#if 0
            /* Try to merge tic marks and border. Mostly useless since border is normally drawn after the tic marks. */
            if (first && (yinc > 0) && (ch == BOX_VERT) && (caca_get_char(cv, x1, y1) == BOX_HOR))
                CACA_put_transparent_char(cv, x1, y1, BOX_DOWN_HOR); /* top inward or bottom outward tics */
            else if (first && (yinc < 0) && (ch == BOX_VERT) && (caca_get_char(cv, x1, y1) == BOX_HOR))
                CACA_put_transparent_char(cv, x1, y1, BOX_UP_HOR); /* bottom inward or top outward tics */
            else
#endif
                CACA_put_transparent_char(cv, x1, y1, ch);
            first = FALSE;
            if(delta > 0)
            {
                x1 += xinc;
                y1 += yinc;
                delta += dpru;
            }
            else
            {
                y1 += yinc;
                delta += dpr;
            }
        }
    }
}


/* Thin line drawing function, using Bresenham's mid-point line
 * scan-conversion algorithm and ASCII art graphics. */
static void
CACA_internal_draw_thin_line(caca_canvas_t *cv, struct line* s)
{
    uint32_t charmapx[2], charmapy[2];
    int x1, y1, x2, y2;
    int dx, dy;
    int yinc;

    if(s->x2 >= s->x1)
    {
        charmapx[0] = (s->y1 > s->y2) ? ',' : '`';
        charmapx[1] = (s->y1 > s->y2) ? '\'' : '.';
        x1 = s->x1; y1 = s->y1; x2 = s->x2; y2 = s->y2;
    }
    else
    {
        charmapx[0] = (s->y1 > s->y2) ? '`' : '.';
        charmapx[1] = (s->y1 > s->y2) ? ',' : '\'';
        x2 = s->x1; y2 = s->y1; x1 = s->x2; y1 = s->y2;
    }

    dx = abs(x2 - x1);
    dy = abs(y2 - y1);

    if(y1 > y2)
    {
        charmapy[0] = ',';
        charmapy[1] = '\'';
        yinc = -1;
    }
    else
    {
        yinc = 1;
        charmapy[0] = '`';
        charmapy[1] = '.';
    }

    if(dx >= dy)
    {
        int dpr = dy << 1;
        int dpru = dpr - (dx << 1);
        int delta = dpr - dx;
        int prev = 0;

        for(; dx>=0; dx--)
        {
            if(delta > 0)
            {
                CACA_put_transparent_char(cv, x1, y1, charmapy[1]);
                x1++;
                y1 += yinc;
                delta += dpru;
                prev = 1;
            }
            else
            {
                if(prev)
                    CACA_put_transparent_char(cv, x1, y1, charmapy[0]);
                else
                    CACA_put_transparent_char(cv, x1, y1, '-');
                x1++;
                delta += dpr;
                prev = 0;
            }
        }
    }
    else
    {
        int dpr = dx << 1;
        int dpru = dpr - (dy << 1);
        int delta = dpr - dy;

        for(; dy >= 0; dy--)
        {
            if(delta > 0)
            {
                CACA_put_transparent_char(cv, x1, y1, charmapx[0]);
                CACA_put_transparent_char(cv, x1 + 1, y1, charmapx[1]);
                x1++;
                y1 += yinc;
                delta += dpru;
            }
            else
            {
                CACA_put_transparent_char(cv, x1, y1, '|');
                y1 += yinc;
                delta += dpr;
            }
        }
    }
}


static void
CACA_draw_thin_line(caca_canvas_t *cv, int x1, int y1, int x2, int y2)
{
	struct line s;

	s.x1 = x1;
	s.y1 = y1;
	s.x2 = x2;
	s.y2 = y2;
	CACA_internal_draw_thin_line(cv, &s);
}


static void
CACA_draw_thin_polyline(caca_canvas_t *cv, int const x[], int const y[], int n)
{
	struct line s;
	int i;

	for (i = 0; i < n; i++) {
		s.x1 = x[i];
		s.y1 = y[i];
		s.x2 = x[i+1];
		s.y2 = y[i+1];
		CACA_internal_draw_thin_line(cv, &s);
	}
}


static int
CACA_internal_draw_box(caca_canvas_t *cv, int x, int y, int w, int h,
                    uint32_t const *chars)
{
    int i, j, xmax, ymax;

    int x2 = x + w - 1;
    int y2 = y + h - 1;

	uint32_t attr_bg = caca_get_attr(cv, x, y);
	uint32_t attr_fg = caca_get_attr(cv, -1, -1);
	caca_set_attr(cv,
				(attr_fg & 0x0003ffff) | /* new foreground color (18 bits) */
				(attr_bg & 0xfffc0000)); /* keep current background color (14 bit) */

    if(x > x2)
    {
        int tmp = x;
        x = x2; x2 = tmp;
    }

    if(y > y2)
    {
        int tmp = y;
        y = y2; y2 = tmp;
    }

    xmax = CACA_xmax;
    ymax = CACA_ymax;

    if(x2 < 0 || y2 < 0 || x > xmax || y > ymax)
        return 0;

    /* Draw edges */
    if(y >= 0)
        for(i = x < 0 ? 1 : x + 1; i < x2 && i < xmax; i++)
            CACA_put_transparent_char(cv, i, y, chars[0]);

    if(y2 <= ymax)
        for(i = x < 0 ? 1 : x + 1; i < x2 && i < xmax; i++)
            CACA_put_transparent_char(cv, i, y2, chars[0]);

    if(x >= 0)
        for(j = y < 0 ? 1 : y + 1; j < y2 && j < ymax; j++)
            CACA_put_transparent_char(cv, x, j, chars[1]);

    if(x2 <= xmax)
        for(j = y < 0 ? 1 : y + 1; j < y2 && j < ymax; j++)
            CACA_put_transparent_char(cv, x2, j, chars[1]);

    /* Draw corners */
    CACA_put_transparent_char(cv, x, y, chars[2]);
    CACA_put_transparent_char(cv, x, y2, chars[3]);
    CACA_put_transparent_char(cv, x2, y, chars[4]);
    CACA_put_transparent_char(cv, x2, y2, chars[5]);

    return 0;
}


static int
CACA_draw_thin_box(caca_canvas_t *cv, int x, int y, int w, int h)
{
    static uint32_t const ascii_chars[] =
    {
        '-', '|', ',', '`', '.', '\''
    };

    return CACA_internal_draw_box(cv, x, y, w, h, ascii_chars);
}


static int
CACA_draw_cp437_box(caca_canvas_t *cv, int x, int y, int w, int h)
{
    static uint32_t const cp437_chars[] =
    {
        /* ─ │ ┌ └ ┐ ┘ */
        0x2500, 0x2502, 0x250c, 0x2514, 0x2510, 0x2518
    };

    return CACA_internal_draw_box(cv, x, y, w, h, cp437_chars);
}


/* Convert RGB to ANSI background color */
static uint8_t
CACA_rgb_to_ansi(uint32_t color)
{
	uint32_t attr;

	attr = (((color & 0xf00000) >> 13) |	/* R: 4 bits */
	        ((color & 0x00f000) >>  9) |	/* G: 4 bits */
	        ((color & 0x0000e0) >>  5) |	/* B: 3 bits */
	          0xe000) << 18;				/* A: 3 bits */
	return caca_attr_to_ansi_bg(attr);
}


/* Find name of nearest matching predefined color.
   This function is similar to libcaca's nearest_ansi() function,
   but uses 16bit color definitions and rounds when mapping
   8 bit per color component to 4 bit. */
static char const *
CACA_get_color_name(uint8_t color)
{
	int dist = 0xfffffff;
	int i, a, b;
	char const * ret = "";
	static const uint16_t ansitab16[16] = {
		0xf000, 0xf00a, 0xf0a0, 0xf0aa, 0xfa00, 0xfa0a, 0xfa50, 0xfaaa,
		0xf555, 0xf55f, 0xf5f5, 0xf5ff, 0xff55, 0xff5f, 0xfff5, 0xffff,
	};

	for (i = 0; i < num_predefined_colors; i++) {
		if (CACA_rgb_to_ansi(pm3d_color_names_tbl[i].value) == color) {
			int d = 0;

			a = (ansitab16[color] >> 8) & 0xf;
			b = (pm3d_color_names_tbl[i].value >> 16) & 0xff;
			if (b < 0xf0) b += 0x07;
			b >>= 4;
			d += (a - b) * (a - b);

			a = (ansitab16[color] >> 4) & 0xf;
			b = (pm3d_color_names_tbl[i].value >>  8) & 0xff;
			if (b < 0xf0) b += 0x07;
			b >>= 4;
			d += (a - b) * (a - b);

			a = (ansitab16[color] >> 0) & 0xf;
			b = (pm3d_color_names_tbl[i].value >>  0) & 0xff;
			if (b < 0xf0) b += 0x07;
			b >>= 4;
			d += (a - b) * (a - b);

			if (d < dist) {
				dist = d;
				ret = pm3d_color_names_tbl[i].key;
			}
			FPRINTF((stderr, "CACA: matching color #%i = %s (%06x)\n", (int)color,
				pm3d_color_names_tbl[i].key, pm3d_color_names_tbl[i].value));
		}
	}
	return ret;
}

/* -------------------------------------------------------------------------
   End of libcaca helper functions. See comment above.
   ------------------------------------------------------------------------- */

/* -------------------------------------------------------------------------
   FIXME: The routines below could be used by other terminals as well.
   They should be moved and be made non-static when that happens.
   ------------------------------------------------------------------------- */

#ifdef USE_MOUSE
/* convenience wrapper for do_event() */
static void
process_event(char type, int mx, int my, int par1, int par2, int winid)
{
    struct gp_event_t ge;

    ge.type = type;
    ge.mx = mx;
    ge.my = my;
    ge.par1 = par1;
    ge.par2 = par2;
    ge.winid = winid;
    do_event(&ge);
}
#endif


static int
gp_kbhit(void)
{
#ifndef WIN32
# ifdef HAVE_SELECT
    struct timeval tv = {0L, 0L};
    fd_set fds;

    FD_ZERO(&fds);
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
# elif (defined(MSDOS) && !defined(__EMX__))
    return kbhit();
# else
#  error "No definition of kbhit() available."
# endif
#else
# ifdef WGP_CONSOLE
    /* FIXME: This snippet actually determines the number of _all_ events,
    not just key presses. But typically this is not a problem. */
    int fd = fileno(stdin);
    if (!isatty(fd))
	return TRUE;
    else {
	HANDLE h = (HANDLE)_get_osfhandle(fd);
	DWORD events = 0;
	GetNumberOfConsoleInputEvents(h, &events);
	return (events > 0);
    }
# else
    Sleep(0);
    return kbhit();
# endif
#endif
}


#endif /* TERM_BODY */

#ifdef TERM_TABLE
TERM_TABLE_START(caca)
	"caca", "Colour ascii art using libcaca, the Colour AsCii Art library",
	CACA_XMAX, CACA_YMAX, 1, 1, 1, 2 /* aspect of character cell */,
	CACA_options, CACA_init, CACA_reset,
	CACA_text, null_scale, CACA_graphics,
	CACA_move, CACA_vector,
	CACA_linetype, CACA_enhanced_put_text, CACA_text_angle, null_justify_text,
	CACA_point, CACA_arrow, set_font_null, NULL /* pointsize */,
	TERM_NO_OUTPUTFILE | TERM_CAN_MULTIPLOT | TERM_BINARY | TERM_ENHANCED_TEXT,
	CACA_suspend, CACA_resume, CACA_fillbox, NULL /* linewidth */,
#ifdef USE_MOUSE
	CACA_waitforinput, CACA_put_tmptext, CACA_set_ruler, CACA_set_cursor, NULL,
#endif
	CACA_make_palette, NULL /* previous_palette */, CACA_color, CACA_filled_polygon,
	CACA_image,
	CACA_enhanced_open, CACA_enhanced_flush, do_enh_writec,
	CACA_layer, CACA_path, 0.0 /* tscale */, CACA_hypertext,
#ifdef EAM_BOXED_TEXT
	CACA_boxed_text,
#endif
	CACA_modify_plots
TERM_TABLE_END(caca)

#undef LAST_TERM
#define LAST_TERM caca_driver

#endif /* TERM_TABLE */


#ifdef TERM_HELP
START_HELP(caca)
"1 caca",
"?commands set terminal caca",
"?set terminal caca",
"?set term caca",
"?terminal caca",
"?term caca",
"?caca",
" The `caca` terminal driver uses `libcaca` to plot using ascii characters.",
" In contrast to the `dumb` terminal it is a full-fledged terminal and",
" includes support for (RGB) colors, box fill, images, rotated text,",
" filled polygons, unicode character encoding and mouse interaction.",
"",
" Syntax:",
"       set terminal caca {{driver | format} {default | <driver> | list}}",
"                         {color | monochrome}",
"                         {{no}inverted}",
"                         {enhanced | noenhanced}",
"                         {background <rgb color>}",
"                         {title \"<plot window title>\"}",
"                         {size <width>,<height>}",
"                         {charset ascii|blocks|unicode}",
"",
" The `driver` option selects the `libcaca` display driver or export `format`.",
" Use `default` to let `libcaca` choose the platform default display driver.",
" The default driver can be changed by setting the environment variable",
" CACA_DRIVER before starting `gnuplot`.",
" Use `set term caca driver list` to print a list of supported output modes.",
"",
" The `color` and `monochrome` options select colored or mono output.",
" Note that this also changes line symbols.",
" Use the `inverted` option if you prefer a black background over the default",
" white. This also changes the color of black default linetypes to white.",
"",
" Enhanced text support can be activated using the `enhanced` option,",
" see `enhanced text`.",
"",
" The title of the output window can be changed with the `title` option, if",
" supported by the `libcaca` driver.",
"",
" The `size` option selects the size of the canvas in characters.",
" The default is 80 by 25.  If supported by the backend, the canvas size will",
" be automatically adjusted to the current window/terminal size.",
" The default size of the \"x11\" and \"gl\" window can be controlled via the",
" CACA_GEOMETRY environment variable.  The geometry of the window of the",
" \"win32\" driver can be controlled and permanently changed via the app menu.",
"",
" The `charset` option selects the character set used for lines, points,",
" filling of polygons and boxes and dithering of images.  Note that some",
" backend/terminal/font combinations might not support some characters of the",
" `blocks` or `unicode` character set.  On Windows it is recommend to use",
" a non-raster font such as \"Lucida Console\" or \"Consolas\".",
"",
" The caca terminal supports mouse interaction. Please beware that some",
" backends of `libcaca` (e.g. slang, ncurses) only update the mouse position",
" on mouse clicks.  Modifier keys (ctrl, alt, shift) are not supported by",
" `libcaca` and are thus unavailable.",
"",
" The default `encoding` of the `caca` terminal is utf8. It also supports",
" the cp437 `encoding`.  The \"x11\" backend supports unicode since libcaca",
" version 0.99.beta17.  Please note that some versions of `libcaca` contain a",
" bug which results in an endless loop if you supply illegal 8bit sequences.",
"",
" The number of colors supported by `libcaca` backends differs.  Most backends",
" support 16 foreground and 16 background colors only, whereas e.g. the ",
" \"x11\" backend supports truecolor.",
"",
" Depending on the terminal and `libcaca` backend, only 8 different background",
" colors might be supported.  Bright colors (with the most most significant bit",
" of the background color set) are then interpreted as indicator for blinking",
" text.  Try using `background rgb \"gray\"` in that case.",
"",
" See also the libcaca web site at",
"^ <a href=\"http://caca.zoy.org/wiki/libcaca\">",
"           http://caca.zoy.org/wiki/libcaca",
"^ </a>",
" and libcaca environment variables",
"^ <a href=\"http://caca.zoy.org/doxygen/libcaca/libcaca-env.html\">",
"           http://caca.zoy.org/doxygen/libcaca/libcaca-env.html",
"^ </a>",
""
"2 caca limitations and bugs",
"?terminal caca limitations",
"?terminal caca bugs",
"?term caca limitations",
"?term caca bugs",
"?caca limitations",
"?caca bugs",
" The `caca` terminal has some known bugs and limitations:",
"",
" Closing the terminal window by clicking the \"X\" on the title line",
" does not work correctly. Press \"q\" to close the window.",
"",
" Unicode support depends on the driver and the terminal.",
" Bright background colors may cause blinking.",
" Modifier keys are not supported for mousing, see `term caca`.",
"",
" Rotated enhanced text, and transparency are not supported.",
" The `size` option is not considered for on-screen display.",
"",
" In order to correctly draw the key box, use",
"",
"       set key width 1 height 1",
"",
" Alignment of enhanced text is wrong if it contains utf8 characters.",
" gnuplot dies when running from a script when using the \"ncurses\" driver.",
" Illegal utf8 sequences might lead to an endless loop in `libcaca` (now",
" fixed upstream).",
" Resizing of x11 windows does not work on some systems.",
" Changing the input focus to the text input window when pressing <space> does",
" not work reliably in wgnuplot."
END_HELP(caca)
#endif /* TERM_HELP */

