Blob Blame History Raw
/* GNUPLOT - canvas.trm */

/*
 * This file is included by ../term.c.
 *
 * This terminal driver supports:
 *   W3C HTML <canvas> tag
 *
 * AUTHOR
 *   Bruce Lueckenhoff, Aug 2008
 *   Bruce_Lueckenhoff@yahoo.com
 *
 * Additions
 *   Ethan A Merritt, Jan 2009
 *	CANVAS_set_color(), CANVAS_make_palette(), CANVAS_fillbox(), fillstyles, 
 *	CANVAS_point(), CANVAS_pointsize()
 *	"name <foo>" option to create only a callable javascript file foo.js
 *	"fsize <F>" option to select font size (default remains 10.0)
 *   Ethan A Merritt, Jan 2009
 *	Prototype mousing code in separate file gnuplot_mouse.js
 *   Ethan A Merritt, Feb 2009
 *	Enhanced text support. Note: character placement could be done more
 *	precisely by moving the enhanced text code into the javascript routines,
 *	where exact character widths are known, and save/restore can be used.
 *   Ethan A Merritt, Mar 2009
 *	Oversampling and client-side zoom/unzoom, hotkeys
 *   Ethan A Merritt, May 2009
 *	Give each plot its own namespace for mousing (allows multiple mouseable
 *	plots in a single HTML document).
 *   Ethan A Merritt, Nov 2010
 *	Dashed line support, butt/rounded line properties
 *	revised javascript with all methods and variables in container gnuplot()
 *   Ethan A Merritt, Feb/Mar/Apr 2011
 *	Optional explicit background
 *	Wrap each plot in a test for gnuplot.hide_plot_N
 *	Handle image data by storing it in a parallel PNG file
 *   Ethan A Merritt, Mar 2012
 *	Hypertext
 *   Ethan A Merritt, Jan 2018
 *	Always apply new color, including alpha channel, to both strokeStyle
 *	and fillStyle. This allows the terminal to honor RGBA colors for
 *	lines and points (added to core code in version 4.6)
 *
 * send your comments or suggestions to (gnuplot-info@lists.sourceforge.net).
 *
 */
#include "driver.h"

#ifdef TERM_REGISTER
register_term(canvas_driver)
#endif

#ifdef TERM_PROTO
TERM_PUBLIC void CANVAS_options __PROTO((void));
TERM_PUBLIC void CANVAS_init __PROTO((void));
TERM_PUBLIC void CANVAS_graphics __PROTO((void));
TERM_PUBLIC int CANVAS_justify_text __PROTO((enum JUSTIFY mode));
TERM_PUBLIC void CANVAS_text __PROTO((void));
TERM_PUBLIC void CANVAS_reset __PROTO((void));
TERM_PUBLIC void CANVAS_linetype __PROTO((int linetype));
TERM_PUBLIC void CANVAS_dashtype __PROTO((int type, t_dashtype *custom_dash_type));
TERM_PUBLIC void CANVAS_fillbox __PROTO((int style, unsigned int x1, unsigned int y1,
					unsigned int width, unsigned int height));
TERM_PUBLIC void CANVAS_linewidth __PROTO((double linewidth));
TERM_PUBLIC void CANVAS_move __PROTO((unsigned int x, unsigned int y));
TERM_PUBLIC void CANVAS_vector __PROTO((unsigned int x, unsigned int y));
TERM_PUBLIC void CANVAS_point __PROTO((unsigned int x, unsigned int y, int number));
TERM_PUBLIC void CANVAS_pointsize __PROTO((double size));
TERM_PUBLIC void CANVAS_put_text __PROTO((unsigned int x, unsigned int y,
					const char *str));
TERM_PUBLIC int CANVAS_text_angle __PROTO((int ang));
TERM_PUBLIC void CANVAS_filled_polygon __PROTO((int, gpiPoint *));
TERM_PUBLIC void CANVAS_set_color __PROTO((t_colorspec *colorspec));
TERM_PUBLIC int CANVAS_make_palette __PROTO((t_sm_palette *palette));
TERM_PUBLIC void CANVAS_layer __PROTO((t_termlayer));
TERM_PUBLIC void CANVAS_path __PROTO((int));
TERM_PUBLIC void CANVAS_hypertext __PROTO((int, const char *));
TERM_PUBLIC int CANVAS_set_font __PROTO((const char *));

TERM_PUBLIC void ENHCANVAS_OPEN __PROTO((char *, double, double, TBOOLEAN, TBOOLEAN, int));
TERM_PUBLIC void ENHCANVAS_FLUSH __PROTO((void));
TERM_PUBLIC void ENHCANVAS_put_text __PROTO((unsigned int, unsigned int, const char *));

#define CANVAS_OVERSAMPLE	10.
#define CANVAS_XMAX		(600 * CANVAS_OVERSAMPLE) 
#define CANVAS_YMAX 		(400 * CANVAS_OVERSAMPLE)
#define CANVASVTIC		(10  * CANVAS_OVERSAMPLE)
#define CANVASHTIC		(10  * CANVAS_OVERSAMPLE)
#define CANVASVCHAR		(10  * CANVAS_OVERSAMPLE)
#define CANVASHCHAR		(8   * CANVAS_OVERSAMPLE)

#endif /* TERM_PROTO */

#ifdef TERM_BODY

#define CANVAS_AXIS_CONST '\1'
#define CANVAS_BORDER_CONST '\2'

static int canvas_x = -1;	/* current X position */
static int canvas_y = -1;	/* current Y position */
static int canvas_xmax = CANVAS_XMAX;
static int canvas_ymax = CANVAS_YMAX;
static int canvas_line_type = LT_UNDEFINED;
static int canvas_dash_type = DASHTYPE_SOLID;
static double canvas_linewidth = 1.0;
static double canvas_dashlength_factor = 1.0;
static double CANVAS_ps = 1;	/* pointsize multiplier */
static double CANVAS_default_fsize = 10;
static double canvas_font_size = 10;
static double canvas_fontscale = 1.0;
static char * canvas_font_name = NULL;
static char *canvas_justify = "";
static int canvas_text_angle = 0;
static int canvas_in_a_path = FALSE;
static int already_closed = FALSE;
static TBOOLEAN canvas_dashed = TRUE;		/* Version 5: dashes always enabled */
static t_linecap canvas_linecap = ROUNDED;
static TBOOLEAN CANVAS_mouseable = FALSE;
static TBOOLEAN CANVAS_standalone = TRUE;
static char CANVAS_background[18] = {'\0'};
static char *CANVAS_name = NULL;
static char *CANVAS_scriptdir = NULL;
static char *CANVAS_title = NULL;
static char *CANVAS_hypertext_text = NULL;

/*
 * Stuff for tracking images stored in separate files
 * to be referenced by canvas.drawImage();
 */
static int CANVAS_imageno = 0;
typedef struct canvas_imagefile {
    int imageno;	/* Used to generate the internal name */
    char *filename;	/* The parallel file that it maps to  */
    struct canvas_imagefile *next;
} canvas_imagefile;
static canvas_imagefile *imagelist = NULL;

static struct {
    int  previous_linewidth;
    double alpha;		/* alpha channel */
    char color[24];		/* rgba(rrr,ggg,bbb,aaaa) */
    char previous_color[24];	/* rgba(rrr,ggg,bbb,aaaa) */
    char previous_fill[24];	/* rgba(rrr,ggg,bbb,aaaa) */
    int  plotno;		/* Which plot are we in? */
} canvas_state;

enum CANVAS_case {
    CANVAS_SIZE, CANVAS_FONT, CANVAS_FSIZE, 
    CANVAS_NAME, CANVAS_STANDALONE, CANVAS_TITLE,
    CANVAS_LINEWIDTH, CANVAS_MOUSING, CANVAS_JSDIR, CANVAS_ENH, CANVAS_NOENH,
    CANVAS_FONTSCALE, CANVAS_SOLID, CANVAS_DASHED, CANVAS_DASHLENGTH,
    CANVAS_ROUNDED, CANVAS_BUTT, CANVAS_SQUARE, CANVAS_BACKGROUND, CANVAS_OTHER
};

static struct gen_table CANVAS_opts[] =
{
    { "font", CANVAS_FONT },
    { "fsize", CANVAS_FSIZE },
    { "name", CANVAS_NAME },
    { "size", CANVAS_SIZE },
    { "standalone", CANVAS_STANDALONE },
    { "mous$ing", CANVAS_MOUSING },
    { "mouse", CANVAS_MOUSING },
    { "js$dir", CANVAS_JSDIR },
    { "enh$anced", CANVAS_ENH },
    { "noenh$anced", CANVAS_NOENH },
    { "lw", CANVAS_LINEWIDTH },
    { "linew$idth", CANVAS_LINEWIDTH },
    { "title", CANVAS_TITLE },
    { "fontscale", CANVAS_FONTSCALE },
    { "solid", CANVAS_SOLID },
    { "dash$ed", CANVAS_DASHED },
    { "dashl$ength", CANVAS_DASHLENGTH },
    { "dl", CANVAS_DASHLENGTH },
    { "round$ed", CANVAS_ROUNDED },
    { "butt", CANVAS_BUTT },
    { "square", CANVAS_SQUARE },
    { "backg$round", CANVAS_BACKGROUND },
    { NULL, CANVAS_OTHER }
};

/* Fill patterns */
#define PATTERN1 "tile.moveTo(0,0); tile.lineTo(32,32); tile.moveTo(0,16); tile.lineTo(16,32); tile.moveTo(16,0); tile.lineTo(32,16);"
#define PATTERN2 "tile.moveTo(0,32); tile.lineTo(32,0); tile.moveTo(0,16); tile.lineTo(16,0); tile.moveTo(16,32); tile.lineTo(32,16);"
#define PATTERN3 "tile.moveTo(8,0); tile.lineTo(32,24); tile.moveTo(0,8); tile.lineTo(24,32); tile.moveTo(24,0); tile.lineTo(32,8); tile.moveTo(0,24); tile.lineTo(8,32); tile.moveTo(8,32); tile.lineTo(32,8); tile.moveTo(0,24); tile.lineTo(24,0); tile.moveTo(24,32); tile.lineTo(32,24); tile.moveTo(0,8); tile.lineTo(8,0);"


static void
CANVAS_start (void)
{
    if (canvas_in_a_path)
	return;
    fprintf(gpoutfile, "ctx.beginPath();\n");
    canvas_in_a_path = TRUE;
    already_closed = FALSE;
}

static void
CANVAS_finish (void)
{
    if (!canvas_in_a_path)
	return;
    fprintf(gpoutfile, "ctx.stroke();\n");

    if (!already_closed)
	fprintf(gpoutfile, "ctx.closePath();\n");
    canvas_in_a_path = FALSE;
    already_closed = TRUE;
}

TERM_PUBLIC void
CANVAS_options()
{
    int canvas_background = 0;

    if (!almost_equals(c_token-1, "termopt$ion")) {
	/* Re-initialize a few things */
	canvas_font_size = CANVAS_default_fsize = 10;
	canvas_fontscale = 1.0;
	CANVAS_standalone = TRUE;
	CANVAS_mouseable = FALSE;
	free(CANVAS_name);
	CANVAS_name = NULL;
	free(CANVAS_title);
	CANVAS_title = NULL;
	free(CANVAS_scriptdir);
	CANVAS_scriptdir = NULL;
	canvas_linewidth = 1.0;
	canvas_dashed = TRUE;
	canvas_dashlength_factor = 1.0;
	CANVAS_background[0] = '\0';
	/* Default to enhanced text mode */
	term->put_text = ENHCANVAS_put_text;
	term->flags |= TERM_ENHANCED_TEXT;
    }

    while (!END_OF_COMMAND) {
	switch(lookup_table(&CANVAS_opts[0],c_token++)) {
	case CANVAS_SIZE:
	    if (END_OF_COMMAND) {
		canvas_xmax = CANVAS_XMAX;
		canvas_ymax = CANVAS_YMAX;
	    } else {
		canvas_xmax = int_expression() * CANVAS_OVERSAMPLE;
		if (equals(c_token,",")) {
		    c_token++;
		    canvas_ymax = int_expression() * CANVAS_OVERSAMPLE;
		}
	    }
	    if (canvas_xmax <= 0)
		canvas_xmax = CANVAS_XMAX;
	    if (canvas_ymax <= 0)
		canvas_ymax = CANVAS_YMAX;
	    term->xmax = canvas_xmax;
	    term->ymax = canvas_ymax;
 	    break;

	case CANVAS_TITLE:
	    CANVAS_title = try_to_get_string();
	    if (!CANVAS_title)
		int_error(c_token,"expecting an HTML title string");
	    break;

	case CANVAS_NAME:
	    CANVAS_name = try_to_get_string();
	    if (!CANVAS_name)
		    int_error(c_token,"expecting a javascript function name");
	    if (CANVAS_name[strspn(CANVAS_name,
	        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_1234567890")])
		    int_error(c_token-1,"illegal javascript function name");
	    CANVAS_standalone = FALSE;
	    break;

	case CANVAS_STANDALONE:
	    CANVAS_standalone = TRUE;
	    break;

	case CANVAS_FONT:
	/* FIXME: See note at CANVAS_set_font() */ 
	    free(canvas_font_name);
	    if (!(canvas_font_name = try_to_get_string()))
		int_error(c_token,"font: expecting string");
	    CANVAS_set_font(canvas_font_name);
	    break;

	case CANVAS_FSIZE:
	    CANVAS_default_fsize = real_expression();
	    if (CANVAS_default_fsize <= 0)
		CANVAS_default_fsize = 10;
	    canvas_font_size = CANVAS_default_fsize;
	    break;

	case CANVAS_MOUSING:
	    CANVAS_mouseable = TRUE;
	    break;

	case CANVAS_JSDIR:
	    CANVAS_scriptdir = try_to_get_string();
	    break;

	case CANVAS_ENH:
	    term->put_text = ENHCANVAS_put_text;
	    term->flags |= TERM_ENHANCED_TEXT;
	    break;

	case CANVAS_NOENH:
	    term->put_text = CANVAS_put_text;
	    term->flags &= ~TERM_ENHANCED_TEXT;
	    break;

	case CANVAS_LINEWIDTH:
	    canvas_linewidth = real_expression();
	    if (canvas_linewidth <= 0)
		canvas_linewidth = 1.0;
	    break;

	case CANVAS_FONTSCALE:
	    canvas_fontscale = real_expression();
	    if (canvas_fontscale <= 0)
		canvas_fontscale = 1.0;
	    break;

	case CANVAS_SOLID:
	case CANVAS_DASHED:
	    /* Version 5 always allows dashes */
	    canvas_dashed = TRUE;
	    break;

	case CANVAS_DASHLENGTH:
	    canvas_dashlength_factor = real_expression();
	    if (canvas_dashlength_factor <= 0.2)
		canvas_dashlength_factor = 1.0;
	    break;

	case CANVAS_ROUNDED:
	    canvas_linecap = ROUNDED;
	    break;

	case CANVAS_BUTT:
	    canvas_linecap = BUTT;
	    break;

	case CANVAS_SQUARE:
	    canvas_linecap = SQUARE;
	    break;

	case CANVAS_BACKGROUND:
	    canvas_background = parse_color_name();
	    sprintf(CANVAS_background," rgb(%03d,%03d,%03d)",
		(canvas_background >> 16) & 0xff,
		(canvas_background >> 8) & 0xff, 
		canvas_background & 0xff);
	    break;

	default:
	    int_warn(c_token-1,"unrecognized terminal option");
 	    break;
	}
    }

    term->v_char = canvas_font_size * canvas_fontscale * CANVAS_OVERSAMPLE;
    term->h_char = canvas_font_size * canvas_fontscale * 0.8 * CANVAS_OVERSAMPLE;

    if (canvas_dashlength_factor != 1.0)
	sprintf(term_options + strlen(term_options), " dashlength %3.1f", canvas_dashlength_factor);
    sprintf(term_options + strlen(term_options), 
	canvas_linecap == ROUNDED ? " rounded" : 
	canvas_linecap == SQUARE ? " square" : " butt");
    sprintf(term_options + strlen(term_options), " size %d,%d", (int)(term->xmax/CANVAS_OVERSAMPLE), (int)(term->ymax/CANVAS_OVERSAMPLE));
    sprintf(term_options + strlen(term_options), "%s fsize %g lw %g", 
	term->put_text == ENHCANVAS_put_text ? " enhanced" : "",
	canvas_font_size, canvas_linewidth);
    sprintf(term_options + strlen(term_options), " fontscale %g", canvas_fontscale);
    if (*CANVAS_background)
	sprintf(term_options + strlen(term_options), " background \"#%06x\"", canvas_background);
    if (CANVAS_name)
	sprintf(term_options + strlen(term_options), " name \"%s\"", CANVAS_name);
    else {
	sprintf(term_options + strlen(term_options), " standalone");
	if (CANVAS_mouseable)
	    sprintf(term_options + strlen(term_options), " mousing");
	if (CANVAS_title)
	    sprintf(term_options + strlen(term_options), " title \"%s\"", CANVAS_title);
    }
    if (CANVAS_scriptdir)
	sprintf(term_options + strlen(term_options), " jsdir \"%s\"", CANVAS_scriptdir);
}


TERM_PUBLIC void
CANVAS_init()
{
}

TERM_PUBLIC void
CANVAS_graphics()
{
#if !defined(VMS)
    int len;
#endif
    /* Force initialization at the beginning of each plot */
    canvas_line_type = LT_UNDEFINED;
    canvas_text_angle = 0;
    canvas_in_a_path = FALSE;
    canvas_state.previous_linewidth = -1;
    canvas_state.previous_color[0] = '\0';
    canvas_state.previous_fill[0] = '\0';
    strcpy(canvas_state.color,"rgba(000,000,000,0.00)");
    canvas_state.plotno = 0;

    /*
     * FIXME: This code could be shared with svg.trm
     *        Figure out where to find javascript support routines
     *        when the page is viewed.
     */
    if (CANVAS_scriptdir == NULL) {
#ifdef GNUPLOT_JS_DIR
# if defined(_WIN32)
	CANVAS_scriptdir = RelativePathToGnuplot(GNUPLOT_JS_DIR);
# else /* !_WIN32 */
	/* use hardcoded _absolute_ path */
	CANVAS_scriptdir = strdup(GNUPLOT_JS_DIR);
# endif
#else
	CANVAS_scriptdir = strdup("");
#endif /* GNUPLOT_JS_DIR */
    }

#if !defined(VMS)
    len = strlen(CANVAS_scriptdir);
# if defined(_WIN32)
    if (*CANVAS_scriptdir && CANVAS_scriptdir[len-1] != '\\' && CANVAS_scriptdir[len-1] != '/') {
	CANVAS_scriptdir = gp_realloc(CANVAS_scriptdir, len+2, "jsdir");
	if (CANVAS_scriptdir[len-1] == '\\') /* use backslash if used in jsdir, otherwise slash */
	    strcat(CANVAS_scriptdir,"\\");
	else
	    strcat(CANVAS_scriptdir,"/");
    }
# else
    if (*CANVAS_scriptdir && CANVAS_scriptdir[len-1] != '/') {
	CANVAS_scriptdir = gp_realloc(CANVAS_scriptdir, len+2, "jsdir");
	strcat(CANVAS_scriptdir,"/");
    }
# endif
#endif

    if (CANVAS_standalone) {
	fprintf(gpoutfile,
		"<!DOCTYPE HTML>\n"
		"<html>\n"
		"<head>\n"
		"<title>%s</title>\n",
		CANVAS_title ? CANVAS_title : "Gnuplot Canvas Graph"
		);
	if (encoding == S_ENC_UTF8 || encoding == S_ENC_DEFAULT)
	    fprintf(gpoutfile,
		"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n"
	    );
	fprintf(gpoutfile,
		"<!--[if IE]><script type=\"text/javascript\" src=\"excanvas.js\"></script><![endif]-->\n"
		"<script src=\"%s%s.js\"></script>\n"
		"<script src=\"%sgnuplot_common.js\"></script>\n"
		, CANVAS_scriptdir
		, (encoding == S_ENC_UTF8) ? "canvasmath" : "canvastext"
		, CANVAS_scriptdir);
	if (canvas_dashed)
	    fprintf(gpoutfile,
		"<script src=\"%sgnuplot_dashedlines.js\"></script>\n"
		, CANVAS_scriptdir);
	if (CANVAS_mouseable) {
	    fprintf(gpoutfile,
		"<script src=\"%sgnuplot_mouse.js\"></script>\n"
		, CANVAS_scriptdir);
	    fprintf(gpoutfile,
		"<script type=\"text/javascript\"> "
		"gnuplot.help_URL = \"%s/canvas_help.html\"; </script>\n"
		, CANVAS_scriptdir);
	} else {
	    fprintf(gpoutfile,
		"<script type=\"text/javascript\">"
		"gnuplot.init = function() {};</script>\n");
	}
	fprintf(gpoutfile,
		"<script type=\"text/javascript\">\n"
		"var canvas, ctx;\n"
		"gnuplot.grid_lines = true;\n"
		"gnuplot.zoomed = false;\n"
		"gnuplot.active_plot_name = \"gnuplot_canvas\";\n\n"
		"function gnuplot_canvas() {\n"
		"canvas = document.getElementById(\"gnuplot_canvas\");\n"
		"ctx = canvas.getContext(\"2d\");\n"
	);

    } else {
	fprintf(gpoutfile,
		"function %s() {\n"
		"canvas = document.getElementById(\"%s\");\n"
		"ctx = canvas.getContext(\"2d\");\n",
		CANVAS_name, CANVAS_name
	);
	fprintf(gpoutfile,
	    "// Suppress refresh on mouseover if this was the plot we just left\n"
	    "if ((gnuplot.active_plot == %s && gnuplot.display_is_uptodate)) return;\n"
	    "else gnuplot.display_is_uptodate = true;\n",
	    CANVAS_name);
	fprintf(gpoutfile,
	    "// Reinitialize mouse tracking and zoom for this particular plot\n"
	    "if ((typeof(gnuplot.active_plot) == \"undefined\" || gnuplot.active_plot != %s)  &&  typeof(gnuplot.mouse_update) != \"undefined\") {\n"
	    "  gnuplot.active_plot_name = \"%s\";\n"
	    "  gnuplot.active_plot = %s;\n"
	    "  canvas.onmousemove = gnuplot.mouse_update;\n"
	    "  canvas.onmouseup = gnuplot.zoom_in;\n"
	    "  canvas.onmousedown = gnuplot.saveclick;\n"
	    "  canvas.onkeypress = gnuplot.do_hotkey;\n"
	    "  if (canvas.attachEvent) {canvas.attachEvent('mouseover', %s);}\n"
	    "  else if (canvas.addEventListener) {canvas.addEventListener('mouseover', %s, false);} \n"
	    "  gnuplot.zoomed = false;\n"
	    "  gnuplot.zoom_axis_width = 0;\n"
	    "  gnuplot.zoom_in_progress = false;\n",
		CANVAS_name, CANVAS_name, CANVAS_name, CANVAS_name, CANVAS_name);
	fprintf(gpoutfile,
	    "  gnuplot.polar_mode = %s;\n"
	    "  gnuplot.polar_theta0 = %d;\n"
	    "  gnuplot.polar_sense = %d;\n"
	    "  ctx.clearRect(0,0,%d,%d);\n"
	    "}\n",
		(polar) ? "true" : "false", (int)theta_origin, (int)theta_direction,
		(int)(term->xmax / CANVAS_OVERSAMPLE), (int)(term->ymax / CANVAS_OVERSAMPLE));
    }

    fprintf(gpoutfile, "// Gnuplot version %s.%s\n", gnuplot_version, gnuplot_patchlevel);
    fprintf(gpoutfile,
	"// short forms of commands provided by gnuplot_common.js\n"
	"function DT  (dt)  {gnuplot.dashtype(dt);};\n"
	"function DS  (x,y) {gnuplot.dashstart(x,y);};\n"
	"function DL  (x,y) {gnuplot.dashstep(x,y);};\n"
	"function M   (x,y) {if (gnuplot.pattern.length > 0) DS(x,y); else gnuplot.M(x,y);};\n"
	"function L   (x,y) {if (gnuplot.pattern.length > 0) DL(x,y); else gnuplot.L(x,y);};\n"
	"function Dot (x,y) {gnuplot.Dot(x/10.,y/10.);};\n"
	"function Pt  (N,x,y,w) {gnuplot.Pt(N,x/10.,y/10.,w/10.);};\n"
	"function R   (x,y,w,h) {gnuplot.R(x,y,w,h);};\n"
	"function T   (x,y,fontsize,justify,string) {gnuplot.T(x,y,fontsize,justify,string);};\n"
	"function TR  (x,y,angle,fontsize,justify,string) {gnuplot.TR(x,y,angle,fontsize,justify,string);};\n"
	"function bp  (x,y) {gnuplot.bp(x,y);};\n"
	"function cfp () {gnuplot.cfp();};\n"
	"function cfsp() {gnuplot.cfsp();};\n"
	"\n"
    );
    fprintf(gpoutfile,
	"gnuplot.hypertext_list = [];\n"
	"gnuplot.on_hypertext = -1;\n"
	"function Hypertext(x,y,w,text) {\n"
	"    newtext = {x:x, y:y, w:w, text:text};\n"
	"    gnuplot.hypertext_list.push(newtext);\n"
	"}\n"
    );
    fprintf(gpoutfile,
	"gnuplot.dashlength = %d;\n",
	(int)(400 * canvas_dashlength_factor)
    );

    fprintf(gpoutfile,
	"ctx.lineCap = \"%s\"; ctx.lineJoin = \"%s\";\n",
	canvas_linecap == ROUNDED ? "round" : canvas_linecap == SQUARE ? "square" : "butt",
	canvas_linecap == ROUNDED ? "round" : "miter"
    );

    if (*CANVAS_background) {
	fprintf(gpoutfile,
	    "ctx.fillStyle = \"%s\";\n"
	    "ctx.fillRect(0,0,%d,%d);\n",
	    CANVAS_background,
	    (int)(term->xmax / CANVAS_OVERSAMPLE),
	    (int)(term->ymax / CANVAS_OVERSAMPLE));
    }

    fprintf(gpoutfile,
	    "CanvasTextFunctions.enable(ctx);\n"
	    "ctx.strokeStyle = \" rgb(215,215,215)\";\n"
	    "ctx.lineWidth = %.1g;\n\n",
	canvas_linewidth
    );
}


TERM_PUBLIC void
CANVAS_text()
{
    CANVAS_finish();

    /* FIXME: I am not sure whether these variable names should always be the */
    /* same, so that they are re-used by all plots in a document, or whether  */
    /* they should be tied to the function name and hence private.            */
    if (TRUE) {
	struct udvt_entry *udv;
	fprintf(gpoutfile, "\n// plot boundaries and axis scaling information for mousing \n");
	fprintf(gpoutfile, "gnuplot.plot_term_xmax = %d;\n", (int)(term->xmax / CANVAS_OVERSAMPLE));
	fprintf(gpoutfile, "gnuplot.plot_term_ymax = %d;\n", (int)(term->ymax / CANVAS_OVERSAMPLE));
	fprintf(gpoutfile, "gnuplot.plot_xmin = %.1f;\n", (double)plot_bounds.xleft / CANVAS_OVERSAMPLE);
	fprintf(gpoutfile, "gnuplot.plot_xmax = %.1f;\n", (double)plot_bounds.xright / CANVAS_OVERSAMPLE);
	fprintf(gpoutfile, "gnuplot.plot_ybot = %.1f;\n", (double)(term->ymax-plot_bounds.ybot) / CANVAS_OVERSAMPLE);
	fprintf(gpoutfile, "gnuplot.plot_ytop = %.1f;\n", (double)(term->ymax-plot_bounds.ytop) / CANVAS_OVERSAMPLE);
	fprintf(gpoutfile, "gnuplot.plot_width = %.1f;\n", (double)(plot_bounds.xright - plot_bounds.xleft) / CANVAS_OVERSAMPLE);
	fprintf(gpoutfile, "gnuplot.plot_height = %.1f;\n", (double)(plot_bounds.ytop - plot_bounds.ybot) / CANVAS_OVERSAMPLE);

	/* Get true axis ranges as used in the plot */
	update_gpval_variables(1);

#define	MOUSE_PARAM( GP_NAME, js_NAME ) \
	if ((udv = add_udv_by_name(GP_NAME))) { \
	    if (udv->udv_value.type == INTGR)   \
		fprintf(gpoutfile, "%s = %d;\n", js_NAME, udv->udv_value.v.int_val); \
	    else if (udv->udv_value.type == CMPLX) \
		fprintf(gpoutfile, "%s = %g;\n", js_NAME, udv->udv_value.v.cmplx_val.real); \
	}
	if (axis_array[FIRST_X_AXIS].datatype != DT_TIMEDATE) {
	    MOUSE_PARAM("GPVAL_X_MIN", "gnuplot.plot_axis_xmin");
	    MOUSE_PARAM("GPVAL_X_MAX", "gnuplot.plot_axis_xmax");
	}
	/* FIXME: Should this inversion be done at a higher level? */
	if (is_3d_plot && splot_map) { 
	    MOUSE_PARAM("GPVAL_Y_MAX", "gnuplot.plot_axis_ymin");
	    MOUSE_PARAM("GPVAL_Y_MIN", "gnuplot.plot_axis_ymax");
	} else {
	    MOUSE_PARAM("GPVAL_Y_MIN", "gnuplot.plot_axis_ymin");
	    MOUSE_PARAM("GPVAL_Y_MAX", "gnuplot.plot_axis_ymax");
	}

	if (polar) {
	    fprintf(gpoutfile, "gnuplot.plot_axis_rmin = %g;\n",
		(R_AXIS.autoscale & AUTOSCALE_MIN) ? 0.0 : R_AXIS.set_min);
	    fprintf(gpoutfile, "gnuplot.plot_axis_rmax = %g;\n", R_AXIS.set_max);
	}

	if ((axis_array[SECOND_X_AXIS].ticmode & TICS_MASK) != NO_TICS) {
	    MOUSE_PARAM("GPVAL_X2_MIN", "gnuplot.plot_axis_x2min");
	    MOUSE_PARAM("GPVAL_X2_MAX", "gnuplot.plot_axis_x2max");
	} else
	    fprintf(gpoutfile, "gnuplot.plot_axis_x2min = \"none\"\n");
	if (axis_array[SECOND_X_AXIS].linked_to_primary 
	&&  axis_array[FIRST_X_AXIS].link_udf->at) {
	    fprintf(gpoutfile, "gnuplot.x2_mapping = function(x) { return x; };");
	    fprintf(gpoutfile, "  // replace returned value with %s\n",
			axis_array[FIRST_X_AXIS].link_udf->definition);
	}
	if ((axis_array[SECOND_Y_AXIS].ticmode & TICS_MASK) != NO_TICS) {
	    MOUSE_PARAM("GPVAL_Y2_MIN", "gnuplot.plot_axis_y2min");
	    MOUSE_PARAM("GPVAL_Y2_MAX", "gnuplot.plot_axis_y2max");
	} else
	    fprintf(gpoutfile, "gnuplot.plot_axis_y2min = \"none\"\n");
	if (axis_array[SECOND_Y_AXIS].linked_to_primary 
	&&  axis_array[FIRST_Y_AXIS].link_udf->at) {
	    fprintf(gpoutfile, "gnuplot.y2_mapping = function(y) { return y; };");
	    fprintf(gpoutfile, "  // replace returned value with %s\n",
			axis_array[FIRST_Y_AXIS].link_udf->definition);
	}
#undef MOUSE_PARAM

	fprintf(gpoutfile, "gnuplot.plot_logaxis_x = %d;\n",
		axis_array[FIRST_X_AXIS].log ? 1: 0);
	fprintf(gpoutfile, "gnuplot.plot_logaxis_y = %d;\n",
		axis_array[FIRST_Y_AXIS].log ? 1: 0);
	if (polar)
	    fprintf(gpoutfile, "gnuplot.plot_logaxis_r = %d;\n",
		axis_array[POLAR_AXIS].log ? 1: 0);

	/* 
	 * Note: mouse_alt_string is set by 'set mouse mouseformat "foo"'
	 * The only strings currently recognized by gnuplot_mouse.js are
	 * "Time" and "Date", but the user could modify gnuplot_mouse.js to
	 * recognize additional formats.
	 */
	if (axis_array[FIRST_X_AXIS].datatype == DT_TIMEDATE) {
	    /* May need to reconstruct time to millisecond precision */
	    fprintf(gpoutfile, "gnuplot.plot_axis_xmin = %.3f;\n",
		    axis_array[FIRST_X_AXIS].min);
	    fprintf(gpoutfile, "gnuplot.plot_axis_xmax = %.3f;\n",
		    axis_array[FIRST_X_AXIS].max);
	    fprintf(gpoutfile, "gnuplot.plot_timeaxis_x = \"%s\";\n",
		(mouse_alt_string) ? mouse_alt_string
		: (mouse_mode == 4) ? "Date"
		: (mouse_mode == 5) ? "Time"
		: "DateTime"
		);
	} else if (axis_array[FIRST_X_AXIS].datatype == DT_DMS) {
	    fprintf(gpoutfile, "gnuplot.plot_timeaxis_x = \"%s\";\n",
		(mouse_alt_string) ? mouse_alt_string : "DMS");
	} else
	    fprintf(gpoutfile, "gnuplot.plot_timeaxis_x = \"\";\n");
	if (axis_array[FIRST_Y_AXIS].datatype == DT_DMS) {
	    fprintf(gpoutfile, "gnuplot.plot_timeaxis_y = \"%s\";\n",
		(mouse_alt_string) ? mouse_alt_string : "DMS");
	} else
	    fprintf(gpoutfile, "gnuplot.plot_timeaxis_y = \"\";\n");

	fprintf(gpoutfile, "gnuplot.plot_axis_width = gnuplot.plot_axis_xmax - gnuplot.plot_axis_xmin;\n");
	fprintf(gpoutfile, "gnuplot.plot_axis_height = gnuplot.plot_axis_ymax - gnuplot.plot_axis_ymin;\n");

    } /* End of section writing out variables for mousing */

    /* This is the end of the javascript plot */
    fprintf(gpoutfile, "}\n");

#ifdef WRITE_PNG_IMAGE
    /* Link internal image structures to external PNG files */
    if (imagelist) {
	canvas_imagefile *thisimage = imagelist;
	char *base_name = CANVAS_name ? CANVAS_name : "gp";

	while (thisimage != NULL) {
	    fprintf(stderr," linking image %d to external file %s\n",
		thisimage->imageno, thisimage->filename);
	    fprintf(gpoutfile, "  var %s_image_%02d = new Image();",
		base_name, thisimage->imageno);
	    fprintf(gpoutfile, "  %s_image_%02d.src = \"%s\";\n",
		base_name, thisimage->imageno, thisimage->filename);
	    imagelist = thisimage->next;
	    free(thisimage->filename);
	    free(thisimage);
	    thisimage = imagelist;
	}
    }
#endif

    if (CANVAS_standalone) {
	fprintf(gpoutfile,
		"</script>\n"
		"<link type=\"text/css\" href=\"%sgnuplot_mouse.css\" rel=\"stylesheet\">\n"
		"</head>\n"
		"<body onload=\"gnuplot_canvas(); gnuplot.init();\" oncontextmenu=\"return false;\">\n\n"
		"<div class=\"gnuplot\">\n",
			CANVAS_scriptdir ? CANVAS_scriptdir : ""
	);
	/* Pattern-fill requires having a canvas element to hold a template
	 * pattern tile.  Define it here but try to hide it (may not work in IE?)
	 */
	fprintf(gpoutfile,
	    "<canvas id=\"Tile\" width=\"32\" height=\"32\" hidden></canvas>\n");

	/* The format of the plot box and in particular the mouse tracking box
	 * are determined by the CSS specs in customizable file gnuplot_mouse.css
	 * We could make this even more customizable by providing an external HTML
	 * template, but in that case the user might as well just create a *.js
	 * file and provide his own wrapping HTML document.
	 */
	if (CANVAS_mouseable) {
	    int i;
	    fprintf(gpoutfile,
		"<table class=\"mbleft\"><tr><td class=\"mousebox\">\n"

		"<table class=\"mousebox\" border=0>\n"
		"  <tr><td class=\"mousebox\">\n"
		"    <table class=\"mousebox\" id=\"gnuplot_mousebox\" border=0>\n"
		"    <tr><td class=\"mbh\"></td></tr>\n"
		"    <tr><td class=\"mbh\">\n"
		"      <table class=\"mousebox\">\n"
		"	<tr>\n"
		"	  <td class=\"icon\"></td>\n"
		"	  <td class=\"icon\" onclick=gnuplot.toggle_grid><img src=\"%sgrid.png\" id=\"gnuplot_grid_icon\" class=\"icon-image\" alt=\"#\" title=\"toggle grid\"></td>\n"
		"	  <td class=\"icon\" onclick=gnuplot.unzoom><img src=\"%spreviouszoom.png\" id=\"gnuplot_unzoom_icon\" class=\"icon-image\" alt=\"unzoom\" title=\"unzoom\"></td>\n"
		"	  <td class=\"icon\" onclick=gnuplot.rezoom><img src=\"%snextzoom.png\" id=\"gnuplot_rezoom_icon\" class=\"icon-image\" alt=\"rezoom\" title=\"rezoom\"></td>\n"
		"	  <td class=\"icon\" onclick=gnuplot.toggle_zoom_text><img src=\"%stextzoom.png\" id=\"gnuplot_textzoom_icon\" class=\"icon-image\" alt=\"zoom text\" title=\"zoom text with plot\"></td>\n"
		"	  <td class=\"icon\" onclick=gnuplot.popup_help()><img src=\"%shelp.png\" id=\"gnuplot_help_icon\" class=\"icon-image\" alt=\"?\" title=\"help\"></td>\n"
		"	</tr>\n",
		CANVAS_scriptdir ? CANVAS_scriptdir : "", CANVAS_scriptdir ? CANVAS_scriptdir : "",
		CANVAS_scriptdir ? CANVAS_scriptdir : "", CANVAS_scriptdir ? CANVAS_scriptdir : "",
		CANVAS_scriptdir ? CANVAS_scriptdir : ""
	    );

	    /* Table row of plot toggle icons goes here */
	    for (i=1; i<=6*((canvas_state.plotno+5)/6); i++) {
		if ((i%6) == 1)
		    fprintf(gpoutfile, "	<tr>\n");
		if (i<=canvas_state.plotno)
		    fprintf(gpoutfile,
			"	  <td class=\"icon\" onclick=gnuplot.toggle_plot(\"gp_plot_%d\")>%d</td>\n",i,i);
		else
		    fprintf(gpoutfile,
			"	  <td class=\"icon\" > </td>\n");
		if ((i%6) == 0)
		    fprintf(gpoutfile, "	</tr>\n");
	    }

	    fprintf(gpoutfile,
		"      </table>\n"
		"  </td></tr>\n"
		"</table></td></tr><tr><td class=\"mousebox\">\n"
	    );

	    fprintf(gpoutfile,
		"<table class=\"mousebox\" id=\"gnuplot_mousebox\" border=1>\n"
		"<tr> <td class=\"mb0\">x&nbsp;</td> <td class=\"mb1\"><span id=\"gnuplot_canvas_x\">&nbsp;</span></td> </tr>\n"
		"<tr> <td class=\"mb0\">y&nbsp;</td> <td class=\"mb1\"><span id=\"gnuplot_canvas_y\">&nbsp;</span></td> </tr>\n"
	    );

	    if ((axis_array[SECOND_X_AXIS].ticmode & TICS_MASK) != NO_TICS)
	      fprintf(gpoutfile,
		"<tr> <td class=\"mb0\">x2&nbsp;</td> <td class=\"mb1\"><span id=\"gnuplot_canvas_x2\">&nbsp;</span></td> </tr>\n");
	    if ((axis_array[SECOND_Y_AXIS].ticmode & TICS_MASK) != NO_TICS)
	      fprintf(gpoutfile,
		"<tr> <td class=\"mb0\">y2&nbsp;</td> <td class=\"mb1\"><span id=\"gnuplot_canvas_y2\">&nbsp;</span></td> </tr>\n");

	    fprintf(gpoutfile,
		"</table></td></tr>\n"
		"</table>\n"
	    );

	    fprintf(gpoutfile,
	        "</td><td>\n"
	    );
	} /* End if (CANVAS_mouseable) */


	fprintf(gpoutfile,
		"<table class=\"plot\">\n"
		"<tr><td>\n"
		"    <canvas id=\"gnuplot_canvas\" width=\"%d\" height=\"%d\" tabindex=\"0\">\n"
		"	Sorry, your browser seems not to support the HTML 5 canvas element\n"
		"    </canvas>\n"
		"</td></tr>\n"
		"</table>\n",
			(int)(term->xmax/CANVAS_OVERSAMPLE), (int)(term->ymax/CANVAS_OVERSAMPLE)
        );

	if (CANVAS_mouseable) {
	    fprintf(gpoutfile,
		"</td></tr></table>\n"
	    );
	}

	fprintf(gpoutfile,
		"</div>\n\n"
		"</body>\n"
		"</html>\n"
        );
    }

    fflush(gpoutfile);
}


TERM_PUBLIC void
CANVAS_reset()
{
    ;
}


TERM_PUBLIC void
CANVAS_linetype(int linetype)
{
    /* NB: These values are manipulated as numbers; */
    /* it does not work to give only the color name */
    static const char * pen_type[17] = {
	" rgb(255,255,255)", /* should be background */
	" rgb(000,000,000)", /* black */
	" rgb(160,160,160)", /* grey */
	" rgb(255,000,000)", /* red */
	" rgb(000,171,000)", /* green */
	" rgb(000,000,225)", /* blue */
	" rgb(190,000,190)", /* purple */ 
	" rgb(000,255,255)", /* cyan */
	" rgb(021,117,069)", /* pine green*/
	" rgb(000,000,148)", /* navy */
	" rgb(255,153,000)", /* orange */
	" rgb(000,153,161)", /* green blue*/
	" rgb(214,214,069)", /* olive*/
	" rgb(163,145,255)", /* cornflower*/
	" rgb(255,204,000)", /* gold*/
	" rgb(214,000,120)", /* mulberry*/
	" rgb(171,214,000)", /* green yellow*/
    };

    // FIXME
    // if (linetype == canvas_line_type) return;

    canvas_line_type = linetype;
    CANVAS_finish();

    if (linetype >= 14)
	linetype %= 14;
    if (linetype <= LT_NODRAW) /* LT_NODRAW, LT_BACKGROUND, LT_UNDEFINED */
	linetype = -3;
    if (linetype == -3 && *CANVAS_background)
	strcpy(canvas_state.color,CANVAS_background);
    else
	strcpy(canvas_state.color,pen_type[linetype + 3]);
    if (strcmp(canvas_state.color, canvas_state.previous_color)) {
	fprintf(gpoutfile, "ctx.strokeStyle = \"%s\";\n", canvas_state.color);
	strcpy(canvas_state.previous_color, canvas_state.color);
    }
}

TERM_PUBLIC void
CANVAS_dashtype(int type, t_dashtype *custom_dash_type)
{
    if (canvas_line_type == LT_AXIS)
	type = DASHTYPE_AXIS;

    switch (type) {

    case DASHTYPE_AXIS:
	fprintf(gpoutfile, "DT(gnuplot.dashpattern3);\n");
	break;

    case DASHTYPE_SOLID:
	if (canvas_dash_type != DASHTYPE_SOLID)
	    fprintf(gpoutfile, "DT(gnuplot.solid);\n");
	break;

    default:
	type = type % 5;
	if (canvas_dash_type != type)
	    fprintf(gpoutfile, "DT(gnuplot.dashpattern%1d);\n", type + 1);
	break;

    case DASHTYPE_CUSTOM:
	if (custom_dash_type) {
	    double length = 0;
	    double cumulative = 0;
	    int i;
	    for (i = 0; i < DASHPATTERN_LENGTH && custom_dash_type->pattern[i] > 0; i++)
		length += custom_dash_type->pattern[i];
	    fprintf(gpoutfile, "DT([");
	    for (i = 0; i < DASHPATTERN_LENGTH && custom_dash_type->pattern[i] > 0; i++) {
		cumulative += custom_dash_type->pattern[i];
		fprintf(gpoutfile, " %4.2f,", cumulative / length);
	    }
	    fprintf(gpoutfile, " 0]);\n");
	}
	break;
    }
    canvas_dash_type = type;
}

TERM_PUBLIC void
CANVAS_move(unsigned int arg_x, unsigned int arg_y)
{
    if (canvas_in_a_path && (canvas_x == arg_x) && (canvas_y == arg_y)) {
        return;
    }
    CANVAS_start();
    fprintf(gpoutfile,
	    "M(%u,%u);\n",
            arg_x, canvas_ymax - arg_y);
    canvas_x = arg_x;
    canvas_y = arg_y;
}

TERM_PUBLIC void
CANVAS_vector(unsigned int arg_x, unsigned int arg_y)
{
    if ((canvas_x == arg_x) && (canvas_y == arg_y))
        return;

    if (!canvas_in_a_path) {
 	/* Force a new path */
	CANVAS_move(canvas_x, canvas_y);
    }

    fprintf(gpoutfile,
	    "L(%u,%u);\n",
            arg_x, canvas_ymax - arg_y
           );
    canvas_x = arg_x;
    canvas_y = arg_y;
}


TERM_PUBLIC int
CANVAS_justify_text(enum JUSTIFY mode)
{
    switch (mode) {
    case (CENTRE):
	canvas_justify = "Center";
	break;
    case (RIGHT):
	canvas_justify = "Right";
	break;
    default:
    case (LEFT):
        canvas_justify = "";
	break;
    }
    return (TRUE);
}

TERM_PUBLIC void
CANVAS_point(unsigned int x, unsigned int y, int number)
{
    double width  = CANVAS_ps * 0.6 * CANVASHTIC;
    int pt = number % 9;

    /* Skip size 0 points;  the alternative would be to draw a Dot instead */
    if (width <= 0 && pt >= 0)
	return;

    CANVAS_finish();

    switch (pt) {
    default:
	fprintf(gpoutfile, "Dot(%d,%d);\n",
		x, (canvas_ymax-y));
	break;
    case 4:
    case 6:
    case 8:
	if (strcmp(canvas_state.previous_fill, canvas_state.color)) {
	    fprintf(gpoutfile, "ctx.fillStyle = \"%s\";\n", canvas_state.color);
	    strcpy(canvas_state.previous_fill, canvas_state.color);
	}
	/* Fall through */
    case 0:
    case 1:
    case 2:
    case 3:
    case 5:
    case 7:
	fprintf(gpoutfile, "Pt(%d,%d,%d,%.1f);\n", pt,
		x, (canvas_ymax-y), width);
	break;
    }

    /* Hypertext support */
    if (CANVAS_hypertext_text) {
	/* EAM DEBUG: embedded \n produce illegal javascript output */
	while (strchr(CANVAS_hypertext_text,'\n'))
		*strchr(CANVAS_hypertext_text,'\n') = '\v';
	fprintf(gpoutfile, "Hypertext(%d,%d,%.1f,\"%s\");\n",
		x, (canvas_ymax-y), width, CANVAS_hypertext_text);
	free(CANVAS_hypertext_text);
	CANVAS_hypertext_text = NULL;
    }
}

TERM_PUBLIC void
CANVAS_pointsize(double ptsize)
{
    if (ptsize < 0)
	CANVAS_ps = 1;
    else
	CANVAS_ps = ptsize;
}


TERM_PUBLIC int
CANVAS_text_angle(int ang)
{
    canvas_text_angle = -1 * ang;
    return TRUE;
}


TERM_PUBLIC void
CANVAS_put_text(unsigned int x, unsigned int y, const char *str)
{
    if (!str || !(*str))
	return;

    CANVAS_finish();

    /* ctx.fillText uses fillStyle rather than strokeStyle */
    if (strcmp(canvas_state.previous_fill, canvas_state.color)) {
	fprintf(gpoutfile, "ctx.fillStyle = \"%s\";\n", canvas_state.color);
	strcpy(canvas_state.previous_fill, canvas_state.color);
    }

    if (0 != canvas_text_angle) {
	fprintf(gpoutfile,
		"TR(%d,%d,%d,%.1f,\"%s\",\"",
		    x, (int)(canvas_ymax + 5*CANVAS_OVERSAMPLE - y),
		    canvas_text_angle, canvas_font_size * canvas_fontscale, canvas_justify);
    } else {
	fprintf(gpoutfile, "T(%d,%d,%.1f,\"%s\",\"",
		x, (int)(canvas_ymax + 5*CANVAS_OVERSAMPLE - y),
		canvas_font_size * canvas_fontscale, canvas_justify);
    }

    /* Sanitize string by escaping quote characters */
    do {
	if (*str == '"' || *str == '\\')
	    fputc('\\', gpoutfile);
	fputc(*str++, gpoutfile);
    } while (*str);

    fprintf(gpoutfile, "\");\n");
}


TERM_PUBLIC void
CANVAS_linewidth(double linewidth)
{
    CANVAS_finish();
    if (canvas_state.previous_linewidth != linewidth) {
	fprintf(gpoutfile, "ctx.lineWidth = %g;\n", linewidth * canvas_linewidth);
	canvas_state.previous_linewidth = linewidth;
    }
}

TERM_PUBLIC void
CANVAS_set_color(t_colorspec *colorspec)
{
    rgb255_color rgb255;
    canvas_state.alpha = 0.0;

    if (colorspec->type == TC_LT) {
	CANVAS_linetype(colorspec->lt);
	return;
    } else if (colorspec->type == TC_RGB) {
	rgb255.r = (colorspec->lt >> 16) & 0xff;
	rgb255.g = (colorspec->lt >> 8) & 0xff;
	rgb255.b = colorspec->lt & 0xff;
	canvas_state.alpha = (double)((colorspec->lt >> 24) & 0xff)/255.;
    } else if (colorspec->type == TC_FRAC) {
	rgb255maxcolors_from_gray(colorspec->value, &rgb255);
    } else
	/* Other color types not yet supported */
	return;

    CANVAS_finish();

    /* Jan 2017: Always allow for alpha channel */
    sprintf(canvas_state.color,"rgba(%03d,%03d,%03d,%4.2f)",
	    rgb255.r, rgb255.g, rgb255.b, 1.0 - canvas_state.alpha);
    /* Jan 2017: Apply color to BOTH strokeStyle and fillStyle */
    if (strcmp(canvas_state.color, canvas_state.previous_color)) {
	fprintf(gpoutfile, "ctx.strokeStyle = \"%s\";\n", canvas_state.color);
	fprintf(gpoutfile, "ctx.fillStyle = \"%s\";\n", canvas_state.color);
	strcpy(canvas_state.previous_color, canvas_state.color);
	strcpy(canvas_state.previous_fill, canvas_state.color);
    }
    canvas_line_type = LT_UNDEFINED;
}

TERM_PUBLIC int
CANVAS_make_palette(t_sm_palette *palette)
{
    /* We can do full RGB color */
    return 0;
}

static char *
CANVAS_fillstyle(int style)
{
    float density = (float)(style >> 4) / 100.;
    int pattern = style >> 4;
    TBOOLEAN transparent_pattern = TRUE;
    static char fillcolor[24];

    switch (style & 0xf) {
	case FS_TRANSPARENT_SOLID:
		sprintf(fillcolor,"rgba(%11.11s,%4.2f)%c",
			&canvas_state.color[5], density, '\0');
		break;
	case FS_EMPTY:
		strcpy(fillcolor,"rgba(255,255,255,0.00)");
		break;
	case FS_PATTERN:
		transparent_pattern = FALSE;
	case FS_TRANSPARENT_PATTERN:
		if (pattern%6 == 3) {
			strcpy(fillcolor,canvas_state.color);
			strcpy(canvas_state.previous_fill, "");
			break;
		}
		/* Write one copy of the pattern into the Tile element. */
		fprintf(gpoutfile,
			"var template = document.getElementById('Tile');\n"
			"var tile = template.getContext('2d');\n"
			"tile.clearRect(0,0,32,32);\n");
		if (!transparent_pattern)
			fprintf(gpoutfile, "tile.fillStyle = \"%s\"; tile.fillRect(0,0,32,32);\n",
				(*CANVAS_background) ? CANVAS_background : "white");
		fprintf(gpoutfile,
			"tile.beginPath();\n");
		switch(pattern%6) {
			case 1: fprintf(gpoutfile,"%s %s\n",PATTERN1,PATTERN2); break;
			case 2: fprintf(gpoutfile,"%s %s %s\n",PATTERN1,PATTERN2,PATTERN3); break;
			case 4: fprintf(gpoutfile,"%s\n",PATTERN1); break;
			case 5: fprintf(gpoutfile,"%s\n",PATTERN2); break;
			default: break;
		}
		fprintf(gpoutfile,
			"tile.strokeStyle=\"%s\"; tile.lineWidth=\"2\"; tile.stroke();\n",
			canvas_state.color);
		/* Then load it as a fill style */
		fprintf(gpoutfile,
			"ctx.fillStyle = ctx.createPattern(template,\"repeat\");\n");
		strcpy(fillcolor,"pattern");
		break;
	case FS_SOLID:
		if (canvas_state.alpha > 0) {
		    sprintf(fillcolor,"rgba(%11.11s,%4.2f)%c",
			&canvas_state.color[5], 1.0 - canvas_state.alpha, '\0');
		} else if (density == 1) {
		    strcpy(fillcolor,canvas_state.color);
		} else {
		    int r = atoi(&canvas_state.color[5]);
		    int g = atoi(&canvas_state.color[9]);
		    int b = atoi(&canvas_state.color[13]);
		    r = (float)r*density + 255.*(1.-density);
		    g = (float)g*density + 255.*(1.-density);
		    b = (float)b*density + 255.*(1.-density);
		    sprintf(fillcolor," rgb(%3d,%3d,%3d)%c", r, g, b, '\0');
		}
		break;
	default:
		/* Use current color, wherever it came from */
		sprintf(fillcolor,"%s%c",canvas_state.color,'\0');
    }

    return fillcolor;
}

TERM_PUBLIC void
CANVAS_filled_polygon(int points, gpiPoint *corners)
{
    int		i;

    CANVAS_finish();

    /* FIXME: I do not understand why this is necessary, but without it */
    /*        a dashed line followed by a filled area fails to fill.    */
    if (canvas_dashed) {
	fprintf(gpoutfile, "DT(gnuplot.solid);\n");
	canvas_line_type = LT_UNDEFINED;
    }

    if (corners->style != FS_OPAQUE && corners->style != FS_DEFAULT) {
	char *fillcolor = CANVAS_fillstyle(corners->style);
	if (strcmp(fillcolor,"pattern"))
	if (strcmp(canvas_state.previous_fill, fillcolor)) {
	    fprintf(gpoutfile, "ctx.fillStyle = \"%s\";\n", fillcolor);
	    strcpy(canvas_state.previous_fill, fillcolor);
	}
    }

    fprintf(gpoutfile,
	    "bp(%d, %d);\n"
            , corners[0].x, canvas_ymax - corners[0].y);

    for (i = 1; i < points; i++) {
	fprintf(gpoutfile,
		"L(%d, %d);\n"
                , corners[i].x, canvas_ymax - corners[i].y);
    }

    if (corners->style != FS_OPAQUE && corners->style != FS_DEFAULT)
	/* Fill with separate fillStyle color */
	fprintf(gpoutfile, "cfp();\n");
    else
	/* Fill with stroke color */
	fprintf(gpoutfile, "cfsp();\n");
}

TERM_PUBLIC void
CANVAS_fillbox(int style, unsigned int x1, unsigned int y1, unsigned int width, unsigned int height)
{
    char *fillcolor = CANVAS_fillstyle(style);

    /* FIXME: I do not understand why this is necessary, but without it */
    /*        a dashed line followed by a filled area fails to fill.    */
	if (canvas_dashed) {
	    fprintf(gpoutfile, "DT(gnuplot.solid);\n");
	    canvas_line_type = LT_UNDEFINED;
	}

    /* Since filled-rectangle is a primitive operation for the canvas element */
    /* it's worth making this a special case rather than using filled_polygon */
	if (strcmp(fillcolor,"pattern"))
	if (strcmp(canvas_state.previous_fill, fillcolor)) {
	    fprintf(gpoutfile, "ctx.fillStyle = \"%s\";\n", fillcolor);
	    strcpy(canvas_state.previous_fill, fillcolor);
	}

	fprintf(gpoutfile, "R(%d,%d,%d,%d);\n",
		x1, canvas_ymax - (y1+height), width, height);
}

TERM_PUBLIC void 
CANVAS_layer(t_termlayer layer)
{
    char *basename = (CANVAS_name) ? CANVAS_name : "gp";

    switch (layer) {

    case TERM_LAYER_BEFORE_PLOT:
	    canvas_state.plotno++;
	    CANVAS_finish();
	    fprintf(gpoutfile,
		"if (typeof(gnuplot.hide_%s_plot_%d) == \"undefined\""
		"|| !gnuplot.hide_%s_plot_%d) {\n",
		basename, canvas_state.plotno, basename, canvas_state.plotno);
	    break;

    case TERM_LAYER_AFTER_PLOT:
	    CANVAS_finish();
	    fprintf(gpoutfile,"} // End %s_plot_%d \n", basename, canvas_state.plotno);
	    canvas_line_type = LT_UNDEFINED;
	    canvas_dash_type = LT_UNDEFINED;
	    canvas_state.previous_linewidth = -1;
	    canvas_state.previous_color[0] = '\0';
	    canvas_state.previous_fill[0] = '\0';
	    break;

    case TERM_LAYER_BEGIN_GRID:
	    fprintf(gpoutfile, "if (gnuplot.grid_lines) {\n"
			"var saveWidth = ctx.lineWidth;\n"
			"ctx.lineWidth = ctx.lineWidth * 0.5;\n");
	    break;

    case TERM_LAYER_END_GRID:
	    fprintf(gpoutfile,
			"ctx.lineWidth = saveWidth;\n"
			"} // grid_lines\n");
	    break;

    case TERM_LAYER_RESET:
    case TERM_LAYER_RESET_PLOTNO:
	    canvas_state.plotno = 0;
	    break;

    default:
	    break;
    }
}

TERM_PUBLIC void
CANVAS_path(int p)
{
    switch (p) {
	case 1: /* Close path */
		fprintf(gpoutfile, "ctx.closePath();\n");
		already_closed = TRUE;
		break;
	case 0:
		break;
    }
}

TERM_PUBLIC void
CANVAS_hypertext(int type, const char *hypertext)
{
    if (type != TERM_HYPERTEXT_TOOLTIP)
	return;

    free(CANVAS_hypertext_text);
    if (hypertext)
	CANVAS_hypertext_text = gp_strdup(hypertext);
    else
	CANVAS_hypertext_text = NULL;
}

/* FIXME: The HTML5 canvas element did not originally provide any	*/
/* support for text output, let alone fonts.There are now text routines	*/
/* in the HTML5 draft spec, but they are not yet widely supported.	*/
/* So for now we will accept a font request, but ignore it except for	*/
/* the size.								*/
/* Eventually the text calls will look something like			*/
/*	ctx.font = "12pt Times";					*/
/*	ctx.textAlign = "center";					*/
/*	ctx.fillText( "Oh goody, text support.", x, y );		*/
/*									*/
TERM_PUBLIC int
CANVAS_set_font(const char *newfont)
{
    int sep;

	if (!newfont || !(*newfont))
	    canvas_font_size = CANVAS_default_fsize;
	else {
	    sep = strcspn(newfont,",");
	    if (newfont[sep] == ',') {
		sscanf(&(newfont[sep+1]), "%lf", &canvas_font_size);
		if (canvas_font_size <= 0)
		    canvas_font_size = CANVAS_default_fsize;
	    }
	}

	term->v_char = canvas_font_size * canvas_fontscale * CANVAS_OVERSAMPLE;
	term->h_char = canvas_font_size * canvas_fontscale * 0.8 * CANVAS_OVERSAMPLE;

    return 1;
}


/* Enhanced text mode support starts here */

static double ENHCANVAS_base = 0.0;
static double ENHCANVAS_fontsize = 0;
static TBOOLEAN ENHCANVAS_opened_string = FALSE;
static TBOOLEAN ENHCANVAS_show = TRUE;
static TBOOLEAN ENHCANVAS_sizeonly = FALSE;
static TBOOLEAN ENHCANVAS_widthflag = TRUE;
static TBOOLEAN ENHCANVAS_overprint = FALSE;

TERM_PUBLIC void
ENHCANVAS_OPEN(
    char *fontname,
    double fontsize, double base,
    TBOOLEAN widthflag, TBOOLEAN showflag,
    int overprint)
{
    static int save_x, save_y;
    /* overprint = 1 means print the base text (leave position in center)
     * overprint = 2 means print the overlying text
     * overprint = 3 means save current position
     * overprint = 4 means restore saved position
     */
    if (overprint == 3) {
	save_x = canvas_x;
	save_y = canvas_y;
	return;
    }
    if (overprint == 4) {
	canvas_x = save_x;
	canvas_y = save_y;
	return;
    }

    if (!ENHCANVAS_opened_string) {
	ENHCANVAS_opened_string = TRUE;
	enhanced_cur_text = &enhanced_text[0];
	ENHCANVAS_fontsize = fontsize;
	ENHCANVAS_base = base * CANVAS_OVERSAMPLE;
	ENHCANVAS_show = showflag;
	ENHCANVAS_widthflag = widthflag;
	ENHCANVAS_overprint = overprint;
    }
}

/*
 * Since we only have the one font, and the character widths are known,
 * we can go to the trouble of actually counting character widths.
 * As it happens, the averages width of ascii characters is 20.
 */
static int canvas_strwidth(char *s)
{
    int width = 0;
    char *end = s + strlen(s);

    while (*s) {
	if ((*s & 0x80) == 0) {
		if      (strchr("iIl|", *s)) width += 8;
		else if (strchr("j`',;:!.", *s)) width += 10;
		else if (strchr("ftr", *s)) width += 12;
		else if (strchr("()[]{}\\", *s)) width += 14;
		else if (strchr(" JTv^_\"*ykLsxz", *s)) width += 16;
		else if (strchr("AceFV?abdEghnopqu", *s)) width += 18;
		else if (strchr("M~<>%W=&@", *s)) width += 24;
		else if (strchr("m", *s)) width += 30;
		else width += 20;
		s++;
		continue;
	}
	else if (encoding != S_ENC_UTF8) s++;
	else if ((*s & 0xE0) == 0xC0) s += 2;
	else if ((*s & 0xF0) == 0xE0) s += 3;
	else s += 4; 
	width += 18;	/* Assumed average width for UTF8 characters */
	if (s > end) break;
    }
    return (width);
}

TERM_PUBLIC void
ENHCANVAS_FLUSH()
{
    double save_fontsize;
    int x,y;
    double w;

    if (ENHCANVAS_opened_string) {
	ENHCANVAS_opened_string = FALSE;
	*enhanced_cur_text = '\0';

	save_fontsize = canvas_font_size;
	x = canvas_x;
	y = canvas_y;
	w = canvas_strwidth(enhanced_text) * CANVAS_OVERSAMPLE * ENHCANVAS_fontsize/25.;

	canvas_font_size = ENHCANVAS_fontsize;
	x += sin((double)canvas_text_angle * M_PI_2/90.) * ENHCANVAS_base;
	y += cos((double)canvas_text_angle * M_PI_2/90.) * ENHCANVAS_base;
	if (ENHCANVAS_show && !ENHCANVAS_sizeonly)
		CANVAS_put_text(x, y, enhanced_text);

	if (ENHCANVAS_overprint == 1) {
	    canvas_x += w * cos((double)canvas_text_angle * M_PI_2/90.)/2;
	    canvas_y -= w * sin((double)canvas_text_angle * M_PI_2/90.)/2;
	} else if (ENHCANVAS_widthflag) {
	    canvas_x += w * cos((double)canvas_text_angle * M_PI_2/90.);
	    canvas_y -= w * sin((double)canvas_text_angle * M_PI_2/90.);
	}
	canvas_font_size = save_fontsize;
    }
}

TERM_PUBLIC void
ENHCANVAS_put_text(unsigned int x, unsigned int y, const char *str)
{
    char *original_string = (char *)str;

    /* Save starting font properties */
    double fontsize = canvas_font_size;
    char *fontname = "";

    if (!strlen(str))
	return;

    if (ignore_enhanced_text || !strpbrk(str, "{}^_@&~")) {
	CANVAS_put_text(x, y, str);
	return;
    }

    /* ctx.fillText uses fillStyle rather than strokeStyle */
    if (strcmp(canvas_state.previous_fill, canvas_state.color)) {
	fprintf(gpoutfile, "ctx.fillStyle = \"%s\";\n", canvas_state.color);
	strcpy(canvas_state.previous_fill, canvas_state.color);
    }

    CANVAS_move(x,y);

    /* Set up global variables needed by enhanced_recursion() */
    enhanced_fontscale = 1.0;
    strncpy(enhanced_escape_format,"%c",sizeof(enhanced_escape_format));
    ENHCANVAS_opened_string = FALSE;
    ENHCANVAS_fontsize = canvas_font_size;
    if (!strcmp(canvas_justify,"Right") || !strcmp(canvas_justify,"Center"))
	ENHCANVAS_sizeonly = TRUE;

    while (*(str = enhanced_recursion((char *)str, TRUE,
			fontname, fontsize, 0.0, TRUE, TRUE, 0))) {
	(term->enhanced_flush)();
	enh_err_check(str);
	if (!*++str)
	    break; /* end of string */
    }

    /* We can do text justification by running the entire top level string */
    /* through 2 times, with the ENHgd_sizeonly flag set the first time.   */
    /* After seeing where the final position is, we then offset the start  */
    /* point accordingly and run it again without the flag set.            */
    if (!strcmp(canvas_justify,"Right") || !strcmp(canvas_justify,"Center")) {
	char *justification = canvas_justify;
	int x_offset = canvas_x - x;
	int y_offset = (canvas_text_angle == 0) ? 0 : canvas_y - y;

	canvas_justify = "";
	ENHCANVAS_sizeonly = FALSE;

	if (!strcmp(justification,"Right")) {
	    ENHCANVAS_put_text(x - x_offset, y - y_offset, original_string);
	} else if (!strcmp(justification,"Center")) {
	    ENHCANVAS_put_text(x - x_offset/2, y - y_offset/2, original_string);
	}
	canvas_justify = justification;
    }

    /* Make sure we leave with the same font properties as on entry */
	canvas_font_size = fontsize;
	ENHCANVAS_base = 0;

    return;
}

#ifdef WRITE_PNG_IMAGE
TERM_PUBLIC void
CANVAS_image (unsigned m, unsigned n, coordval *image, gpiPoint *corner, t_imagecolor color_mode) 
{
    char *image_file;
    char *base_name = CANVAS_name ? CANVAS_name : "gp";
    canvas_imagefile *thisimage = NULL;
   
    /* Write the image to a png file */
    image_file = gp_alloc(strlen(base_name)+16, "CANVAS_image");
    sprintf(image_file, "%s_image_%02d.png", base_name, ++CANVAS_imageno);
    write_png_image (m, n, image, color_mode, image_file);

    /* Map it onto the terminals coordinate system. */
    fprintf(gpoutfile, "gnuplot.ZI(%s_image_%02d, %d, %d, %d, %d, %d, %d);\n",
	base_name, CANVAS_imageno, m, n,
	corner[0].x, canvas_ymax - corner[0].y, 
	corner[1].x, canvas_ymax - corner[1].y);

    /* Maintain a linked list of imageno|filename pairs */
    /* so that we can load them at the end of the plot. */
    thisimage = gp_alloc(sizeof(canvas_imagefile), "canvas imagefile");
    thisimage->imageno = CANVAS_imageno;
    thisimage->filename = image_file;
    thisimage->next = imagelist;
    imagelist = thisimage;

}
#endif

#endif /* TERM_BODY */

#ifdef TERM_TABLE
TERM_TABLE_START(canvas_driver)
    "canvas", "HTML Canvas object",
    CANVAS_XMAX, CANVAS_YMAX, CANVASVCHAR, CANVASHCHAR,
    CANVASVTIC, CANVASHTIC, CANVAS_options, CANVAS_init, CANVAS_reset,
    CANVAS_text, null_scale, CANVAS_graphics, CANVAS_move, CANVAS_vector,
    CANVAS_linetype, CANVAS_put_text, CANVAS_text_angle,
    CANVAS_justify_text, CANVAS_point, do_arrow, 
    CANVAS_set_font,
    CANVAS_pointsize,
    TERM_CAN_MULTIPLOT|TERM_ALPHA_CHANNEL|TERM_LINEWIDTH|TERM_CAN_DASH,
    NULL, NULL, CANVAS_fillbox, CANVAS_linewidth
#ifdef USE_MOUSE
    , NULL, NULL, NULL, NULL, NULL
#endif
    , CANVAS_make_palette, NULL, CANVAS_set_color
    , CANVAS_filled_polygon
#ifdef WRITE_PNG_IMAGE
    , CANVAS_image
#else
    , NULL
#endif
    , ENHCANVAS_OPEN, ENHCANVAS_FLUSH, do_enh_writec
    , CANVAS_layer
    , CANVAS_path		/* path */
    , 1.0			/* Should this be CANVAS_OVERSAMPLE? */
    , CANVAS_hypertext
#ifdef EAM_BOXED_TEXT
    , NULL			/* boxed text */
#endif 
    , NULL			/* modify_plots */
    , CANVAS_dashtype
TERM_TABLE_END(canvas_driver)

#undef LAST_TERM
#define LAST_TERM canvas_driver

#endif /* TERM_TABLE */

#ifdef TERM_HELP
START_HELP(canvas)
"1 canvas",
"?commands set terminal canvas",
"?set terminal canvas",
"?set term canvas",
"?terminal canvas",
"?term canvas",
"",
" The `canvas` terminal creates a set of javascript commands that draw onto the",
" HTML5 canvas element.",
" Syntax:",
"       set terminal canvas {size <xsize>, <ysize>} {background <rgb_color>}",
"                           {font {<fontname>}{,<fontsize>}} | {fsize <fontsize>}",
"                           {{no}enhanced} {linewidth <lw>}",
"                           {rounded | butt | square}",
"                           {dashlength <dl>}",
"                           {standalone {mousing} | name '<funcname>'}",
"                           {jsdir 'URL/for/javascripts'}",
"                           {title '<some string>'}",
"",
" where <xsize> and <ysize> set the size of the plot area in pixels.",
" The default size in standalone mode is 600 by 400 pixels.",
" The default font size is 10.",
"",
" NB: Only one font is available, the ascii portion of Hershey simplex Roman",
" provided in the file canvastext.js. You can replace this with the file",
" canvasmath.js, which contains also UTF-8 encoded Hershey simplex Greek and",
" math symbols. For consistency with other terminals, it is also possible to",
" use `font \"name,size\"`. Currently the font `name` is ignored, but browser",
" support for named fonts is likely to arrive eventually.",
"",
" The default `standalone` mode creates an html page containing javascript",
" code that renders the plot using the HTML 5 canvas element.  The html page",
" links to two required javascript files 'canvastext.js' and 'gnuplot_common.js'.",
" An additional file 'gnuplot_dashedlines.js' is needed to support dashed lines.",
" By default these point to local files, on unix-like systems usually in",
" directory /usr/local/share/gnuplot/<version>/js.  See installation notes for",
" other platforms. You can change this by using the `jsdir` option to specify",
" either a different local directory or a general URL.  The latter is usually",
" appropriate if the plot is exported for viewing on remote client machines.",
"",
" All plots produced by the canvas terminal are mouseable.  The additional",
" keyword `mousing` causes the `standalone` mode to add a mouse-tracking box",
" underneath the plot. It also adds a link to a javascript file",
" 'gnuplot_mouse.js' and to a stylesheet for the mouse box 'gnuplot_mouse.css'",
" in the same local or URL directory as 'canvastext.js'.",
"",
" The `name` option creates a file containing only javascript. Both the",
" javascript function it contains and the id of the canvas element that it",
" draws onto are taken from the following string parameter.  The commands",
"       set term canvas name 'fishplot'",
"       set output 'fishplot.js'",
" will create a file containing a javascript function fishplot() that will",
" draw onto a canvas with id=fishplot.  An html page that invokes this",
" javascript function must also load the canvastext.js function as described",
" above.  A minimal html file to wrap the fishplot created above might be:",
"",
"       <html>",
"       <head>",
"           <script src=\"canvastext.js\"></script>",
"           <script src=\"gnuplot_common.js\"></script>",
"       </head>",
"       <body onload=\"fishplot();\">",
"           <script src=\"fishplot.js\"></script>",
"           <canvas id=\"fishplot\" width=600 height=400>",
"               <div id=\"err_msg\">No support for HTML 5 canvas element</div>",
"           </canvas>",
"       </body>",
"       </html>",
"",
" The individual plots drawn on this canvas will have names fishplot_plot_1,",
" fishplot_plot_2, and so on. These can be referenced by external javascript",
" routines, for example gnuplot.toggle_visibility(\"fishplot_plot_2\").",
""
END_HELP(canvas)
#endif /* TERM_HELP */