help_command()
{
    static char *helpbuf = NULL;
    static char *prompt = NULL;
    static int toplevel = 1;
    int base;			/* index of first char AFTER help string */
    int len;			/* length of current help string */
    TBOOLEAN more_help;
    TBOOLEAN only;		/* TRUE if only printing subtopics */
    TBOOLEAN subtopics;		/* 0 if no subtopics for this topic */
    int start;			/* starting token of help string */
    char *help_ptr;		/* name of help file */
# if defined(SHELFIND)
    static char help_fname[256] = "";	/* keep helpfilename across calls */
# endif

    if ((help_ptr = getenv("GNUHELP")) == (char *) NULL)
# ifndef SHELFIND
	/* if can't find environment variable then just use HELPFILE */
#  if defined(MSDOS) || defined(OS2)
	help_ptr = HelpFile;
#  else
	help_ptr = HELPFILE;
#  endif

# else				/* !SHELFIND */
    /* try whether we can find the helpfile via shell_find. If not, just
       use the default. (tnx Andreas) */

    if (!strchr(HELPFILE, ':') && !strchr(HELPFILE, '/') &&
	!strchr(HELPFILE, '\\')) {
	if (strlen(help_fname) == 0) {
	    strcpy(help_fname, HELPFILE);
	    if (shel_find(help_fname) == 0) {
		strcpy(help_fname, HELPFILE);
	    }
	}
	help_ptr = help_fname;
    } else {
	help_ptr = HELPFILE;
    }
# endif				/* !SHELFIND */

    /* Since MSDOS DGROUP segment is being overflowed we can not allow such  */
    /* huge static variables (1k each). Instead we dynamically allocate them */
    /* on the first call to this function...                                 */
    if (helpbuf == NULL) {
	helpbuf = gp_alloc(MAX_LINE_LEN, "help buffer");
	prompt = gp_alloc(MAX_LINE_LEN, "help prompt");
	helpbuf[0] = prompt[0] = 0;
    }
    if (toplevel)
	helpbuf[0] = prompt[0] = 0;	/* in case user hit ^c last time */

    /* if called recursively, toplevel == 0; toplevel must == 1 if called
     * from command() to get the same behaviour as before when toplevel
     * supplied as function argument
     */
    toplevel = 1;

    len = base = strlen(helpbuf);

    start = ++c_token;

    /* find the end of the help command */
    while (!(END_OF_COMMAND))
	c_token++;

    /* copy new help input into helpbuf */
    if (len > 0)
	helpbuf[len++] = ' ';	/* add a space */
    capture(helpbuf + len, start, c_token - 1, MAX_LINE_LEN - len);
    squash_spaces(helpbuf + base, 1);	/* only bother with new stuff */
    len = strlen(helpbuf);

    /* now, a lone ? will print subtopics only */
    if (strcmp(helpbuf + (base ? base + 1 : 0), "?") == 0) {
	/* subtopics only */
	subtopics = 1;
	only = TRUE;
	helpbuf[base] = NUL;	/* cut off question mark */
    } else {
	/* normal help request */
	subtopics = 0;
	only = FALSE;
    }

    switch (help(helpbuf, help_ptr, &subtopics)) {
    case H_FOUND:{
	    /* already printed the help info */
	    /* subtopics now is true if there were any subtopics */
	    screen_ok = FALSE;

	    do {
		if (subtopics && !only) {
		    /* prompt for subtopic with current help string */
		    if (len > 0) {
			strcpy (prompt, "Subtopic of ");
			strncat (prompt, helpbuf, MAX_LINE_LEN - 16);
			strcat (prompt, ": ");
		    } else
			strcpy(prompt, "Help topic: ");
		    read_line(prompt, 0);
		    num_tokens = scanner(&gp_input_line, &gp_input_line_len);
		    c_token = 0;
		    more_help = !(END_OF_COMMAND);
		    if (more_help) {
			c_token--;
			toplevel = 0;
			/* base for next level is all of current helpbuf */
			help_command();
		    }
		} else
		    more_help = FALSE;
	    } while (more_help);

	    break;
	}
    case H_NOTFOUND:
	printf("Sorry, no help for '%s'\n", helpbuf);
	break;
    case H_ERROR:
	perror(help_ptr);
	break;
    default:
	int_error(NO_CARET, "Impossible case in switch");
	break;
    }

    helpbuf[base] = NUL;	/* cut it off where we started */
}


successive failures of "set print <foo>" could cause double-free
Bug #2312

old_if_command(struct at_type *expr)
{
    struct value condition;
    char *if_start, *if_end;
    char *else_start = NULL;

    if (clause_depth > 0)
	int_error(c_token,"Old-style if/else statement encountered inside brackets");

    evaluate_at(expr, &condition);

    /* find start and end of the "if" part */
    if_start = &gp_input_line[ token[c_token].start_index ];
    while ((c_token < num_tokens) && !equals(c_token,"else"))
	c_token++;

    if (equals(c_token,"else")) {
	if_end = &gp_input_line[ token[c_token].start_index - 1 ];
	*if_end = '\0';
	else_start = &gp_input_line[ token[c_token].start_index + token[c_token].length ];
    }

    if (real(&condition) != 0.0)
	do_string(if_start);
    else if (else_start)
	do_string(else_start);

    c_token = num_tokens = 0;		/* discard rest of line */
}

gnuplot 5.5 is affected by double free when executing print_set_output. This may result in context-dependent arbitrary code execution.


call_command()
{
    char *save_file = NULL;

    c_token++;
    save_file = try_to_get_string();

    if (!save_file)
	int_error(c_token, "expecting filename");
    gp_expand_tilde(&save_file);

    /* Argument list follows filename */
    load_file(loadpath_fopen(save_file, "r"), save_file, 2);
}


exit_command()
{
    /* If the command is "exit gnuplot" then do so */
    if (equals(c_token+1,"gnuplot"))
	gp_exit(EXIT_SUCCESS);

    if (equals(c_token+1,"status")) {
	int status;
	c_token += 2;
	status = int_expression();
	gp_exit(status);
    }

    /* exit error 'error message'  returns to the top command line */
    if (equals(c_token+1,"error")) {
	c_token += 2;
	int_error(NO_CARET, try_to_get_string());
    }

    /* else graphics will be tidied up in main */
    command_exit_requested = 1;
}


print_command()
{
    struct value a;
    /* space printed between two expressions only */
    TBOOLEAN need_space = FALSE;
    char *dataline = NULL;
    size_t size = 256;
    size_t len = 0;

    if (!print_out)
	print_out = stderr;
    if (print_out_var != NULL) { /* print to datablock */
	dataline = (char *) gp_alloc(size, "dataline");
	*dataline = NUL;
    }
    screen_ok = FALSE;
    do {
	++c_token;
	if (equals(c_token, "$") && isletter(c_token+1) && !equals(c_token+2,"[")) {
	    char *datablock_name = parse_datablock_name();
	    char **line = get_datablock(datablock_name);

	    /* Printing a datablock into itself would cause infinite recursion */
	    if (print_out_var && !strcmp(datablock_name, print_out_name))
		continue;

	    while (line && *line) {
		if (print_out_var != NULL)
		    append_to_datablock(&print_out_var->udv_value, strdup(*line));
		else
		    fprintf(print_out, "%s\n", *line);
		line++;
	    }
	    continue;
	}
	if (type_udv(c_token) == ARRAY && !equals(c_token+1, "[")) {
	    udvt_entry *array = add_udv(c_token++);
	    save_array_content(print_out, array->udv_value.v.value_array);
	    continue;
	}
	const_express(&a);
	if (a.type == STRING) {
	    if (dataline != NULL)
		len = strappend(&dataline, &size, len, a.v.string_val);
	    else
		fputs(a.v.string_val, print_out);
	    need_space = FALSE;
	} else {
	    if (need_space) {
		if (dataline != NULL)
		    len = strappend(&dataline, &size, len, " ");
		else
		    putc(' ', print_out);
	    }
	    if (dataline != NULL)
		len = strappend(&dataline, &size, len, value_to_str(&a, FALSE));
	    else
		disp_value(print_out, &a, FALSE);
	    need_space = TRUE;
	}
	free_value(&a);

    } while (!END_OF_COMMAND && equals(c_token, ","));

    if (dataline != NULL) {
	append_multiline_to_datablock(&print_out_var->udv_value, dataline);
    } else {
	(void) putc('\n', print_out);
	fflush(print_out);
    }
}


bind_command()
{
    char* lhs = NULL;
    char* rhs = NULL;
    TBOOLEAN allwindows = FALSE;
    ++c_token;

    if (almost_equals(c_token,"all$windows")) {
	allwindows = TRUE;
	c_token++;
    }

    /* get left hand side: the key or key sequence
     * either (1) entire sequence is in quotes
     * or (2) sequence goes until the first whitespace
     */
    if (END_OF_COMMAND) {
	; /* Fall through */
    } else if (isstringvalue(c_token) && (lhs = try_to_get_string())) {
	FPRINTF((stderr,"Got bind quoted lhs = \"%s\"\n",lhs));
    } else {
	char *first = gp_input_line + token[c_token].start_index;
	int size = strcspn(first, " \";");
	lhs = gp_alloc(size + 1, "bind_command->lhs");
	strncpy(lhs, first, size);
	lhs[size] = '\0';
	FPRINTF((stderr,"Got bind unquoted lhs = \"%s\"\n",lhs));
	while (gp_input_line + token[c_token].start_index < first+size)
	    c_token++;
    }

    /* get right hand side: the command to bind
     * either (1) quoted command
     * or (2) the rest of the line
     */
    if (END_OF_COMMAND) {
	; /* Fall through */
    } else if (isstringvalue(c_token) && (rhs = try_to_get_string())) {
	FPRINTF((stderr,"Got bind quoted rhs = \"%s\"\n",rhs));
    } else {
	int save_token = c_token;
	while (!END_OF_COMMAND)
	    c_token++;
	m_capture( &rhs, save_token, c_token-1 );
	FPRINTF((stderr,"Got bind unquoted rhs = \"%s\"\n",rhs));
    }

    /* bind_process() will eventually free lhs / rhs ! */
    bind_process(lhs, rhs, allwindows);

}


clear_command()
{

    term_start_plot();

    if (multiplot && term->fillbox) {
	int xx1 = xoffset * term->xmax;
	int yy1 = yoffset * term->ymax;
	unsigned int width = xsize * term->xmax;
	unsigned int height = ysize * term->ymax;
	(*term->fillbox) (0, xx1, yy1, width, height);
    }
    term_end_plot();

    screen_ok = FALSE;
    c_token++;

}


else_command()
{
    if_else_command(if_open_for_else ? IF_FALSE : IF_TRUE);
    return;
}


clause_reset_after_error()
{
    if (clause_depth)
	FPRINTF((stderr,"CLAUSE RESET after error at depth %d\n",clause_depth));
    clause_depth = 0;
    iteration_depth = 0;
}


do_shell()
{
    screen_ok = FALSE;
    c_token++;

    if (user_shell) {
#  if defined(_WIN32)
	if (WinExec(user_shell, SW_SHOWNORMAL) <= 32)
#  elif defined(__DJGPP__)
	if (system(user_shell) == -1)
#  else
	if (spawnl(P_WAIT, user_shell, NULL) == -1)
#  endif /* !(_WIN32 || __DJGPP__) */
	    os_error(NO_CARET, "unable to spawn shell");
    }
}


string_expand_macros()
{
	if (expand_1level_macros() && expand_1level_macros()
	&&  expand_1level_macros() && expand_1level_macros())
	    int_error(NO_CARET, "Macros nested too deeply");
}


new_clause(int clause_start, int clause_end)
{
    char *clause = gp_alloc(clause_end - clause_start, "clause");
    memcpy(clause, &gp_input_line[clause_start+1], clause_end - clause_start);
    clause[clause_end - clause_start - 1] = '\0';
    return clause;
}


help_command()
{
    int first = c_token;

    while (!END_OF_COMMAND)
	++c_token;

    strcpy(Help, "GNUPLOT ");
    capture(Help + 8, first, c_token - 1, sizeof(Help) - 9);
    help_desc.dsc$w_length = strlen(Help);
    if ((vaxc$errno = lbr$output_help(lib$put_output, 0, &help_desc,
				      &helpfile_desc, 0, lib$get_input)) != SS$_NORMAL)
	os_error(NO_CARET, "can't open GNUPLOT$HELP");
}


report_error(int ierr)
{
    int reported_error;

    /* FIXME:  This does not seem to report all reasonable errors correctly */
    if (ierr == -1 && errno != 0)
	reported_error = errno;
    else
	reported_error = WEXITSTATUS(ierr);

    fill_gpval_integer("GPVAL_SYSTEM_ERRNO", reported_error);
    if (reported_error == 127)
	fill_gpval_string("GPVAL_SYSTEM_ERRMSG", "command not found or shell failed");
    else
	fill_gpval_string("GPVAL_SYSTEM_ERRMSG", strerror(reported_error));

    return reported_error;
}


save_command()
{
    FILE *fp;
    char *save_file = NULL;
    TBOOLEAN append = FALSE;
    int what;

    c_token++;
    what = lookup_table(&save_tbl[0], c_token);

    switch (what) {
	case SAVE_FUNCS:
	case SAVE_SET:
	case SAVE_TERMINAL:
	case SAVE_VARS:
	case SAVE_FIT:
	case SAVE_DATABLOCKS:
	    c_token++;
	    break;
	default:
	    break;
    }

    save_file = try_to_get_string();
    if (!save_file)
	int_error(c_token, "expecting filename");
    if (equals(c_token, "append")) {
	append = TRUE;
	c_token++;
    }
#ifdef PIPES
    if (save_file[0]=='|') {
	restrict_popen();
	fp = popen(save_file+1,"w");
    } else
#endif
    {
    gp_expand_tilde(&save_file);
#ifdef _WIN32
    fp = !strcmp(save_file,"-") ? stdout
	: loadpath_fopen(save_file, append?"a":"w");
#else
    fp = !strcmp(save_file,"-") ? stdout
	: fopen(save_file, append?"a":"w");
#endif
    }

    if (!fp)
	os_error(c_token, "Cannot open save file");

    switch (what) {
    case SAVE_FUNCS:
	    save_functions(fp);
	break;
    case SAVE_SET:
	    save_set(fp);
	break;
    case SAVE_TERMINAL:
	    save_term(fp);
	break;
    case SAVE_VARS:
	    save_variables(fp);
	break;
    case SAVE_FIT:
	    save_fit(fp);
	break;
    case SAVE_DATABLOCKS:
	    save_datablocks(fp);
	break;
    default:
	    save_all(fp);
    }

    if (stdout != fp) {
#ifdef PIPES
	if (save_file[0] == '|')
	    (void) pclose(fp);
	else
#endif
	    (void) fclose(fp);
    }

    free(save_file);
}


find_clause(int *clause_start, int *clause_end)
{
    int i, depth;

    *clause_start = token[c_token].start_index;
    for (i=++c_token, depth=1; i<num_tokens; i++) {
	if (equals(i,"{"))
	    depth++;
	else if (equals(i,"}"))
	    depth--;
	if (depth == 0)
	    break;
    }
    *clause_end = token[i].start_index;

    return (i+1);
}


fgets_ipc(
    char *dest,			/* string to fill */
    int len)			/* size of it */
{
#ifdef USE_MOUSE
    if (term && term->waitforinput) {
	/* This a mouseable terminal --- must expect input from it */
	int c;			/* char gotten from waitforinput() */
	size_t i=0;		/* position inside dest */

	dest[0] = '\0';
	for (i=0; i < len-1; i++) {
	    c = term->waitforinput(0);
	    if ('\n' == c) {
		dest[i] = '\n';
		i++;
		break;
	    } else if (EOF == c) {
		dest[i] = '\0';
		return (char*) 0;
	    } else {
		dest[i] = c;
	    }
	}
	dest[i] = '\0';
	return dest;
    } else
#endif
	return fgets(dest, len, stdin);
}


splot_command()
{
    plot_token = c_token++;
    plotted_data_from_stdin = FALSE;
    refresh_nplots = 0;
    SET_CURSOR_WAIT;
#ifdef USE_MOUSE
    plot_mode(MODE_SPLOT);
    add_udv_by_name("MOUSE_X")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_Y")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_X2")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_Y2")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_BUTTON")->udv_value.type = NOTDEFINED;
#endif
    plot3drequest();
    /* Clear "hidden" flag for any plots that may have been toggled off */
    if (term->modify_plots)
	term->modify_plots(MODPLOTS_SET_VISIBLE, -1);
    SET_CURSOR_ARROW;
}


print_set_output(char *name, TBOOLEAN datablock, TBOOLEAN append_p)
{
    if (print_out && print_out != stderr && print_out != stdout) {
#ifdef PIPES
	if (print_out_name[0] == '|') {
	    if (0 > pclose(print_out))
		perror(print_out_name);
	} else
#endif
	    if (0 > fclose(print_out))
		perror(print_out_name);
	print_out = stderr;
    }

    free(print_out_name);
    print_out_name = NULL;
    print_out_var = NULL;

    if (! name) {
	print_out = stderr;
	return;
    }

    if (strcmp(name, "-") == 0) {
	print_out = stdout;
	return;
    }

#ifdef PIPES
    if (name[0] == '|') {
	restrict_popen();
	print_out = popen(name + 1, "w");
	if (!print_out)
	    perror(name);
	else
	    print_out_name = name;
	return;
    }
#endif

    if (!datablock) {
	print_out = fopen(name, append_p ? "a" : "w");
	if (!print_out) {
	    perror(name);
	    return;
	}
    } else {
	print_out_var = add_udv_by_name(name);
	if (!append_p)
	    gpfree_datablock(&print_out_var->udv_value);
	/* If this is not an existing datablock to be appended */
	/* then make it a new empty datablock */
	if (print_out_var->udv_value.type != DATABLOCK) {
	    free_value(&print_out_var->udv_value);
	    print_out_var->udv_value.type = DATABLOCK;
	    print_out_var->udv_value.v.data_array = NULL;
	}
    }

    print_out_name = name;
}


do_system(const char *cmd)
{

     if (!cmd)
	return;

    /* gp_input_line is filled by read_line or load_file, but
     * line_desc length is set only by read_line; adjust now
     */
    line_desc.dsc$w_length = strlen(cmd);
    line_desc.dsc$a_pointer = (char *) cmd;

    if ((vaxc$errno = lib$spawn(&line_desc)) != SS$_NORMAL)
	os_error(NO_CARET, "spawn error");

    (void) putc('\n', stderr);

}


begin_clause()
{
    clause_depth++;
    c_token++;
    return;
}


update_command()
{
    int_error(NO_CARET, "DEPRECATED command 'update', please use 'save fit' instead");
}


array_command()
{
    int nsize = 0;	/* Size of array when we leave */
    int est_size = 0;	/* Estimated size */
    struct udvt_entry *array;
    struct value *A;
    int i;

    /* Create or recycle a udv containing an array with the requested name */
    if (!isletter(++c_token))
	int_error(c_token, "illegal variable name");
    array = add_udv(c_token);
    free_value(&array->udv_value);
    c_token++;

    if (equals(c_token, "[")) {
	c_token++;
	nsize = int_expression();
	if (!equals(c_token++,"]"))
	    int_error(c_token-1, "expecting array[size>0]");
    } else if (equals(c_token, "=") && equals(c_token+1, "[")) {
	/* Estimate size of array by counting commas in the initializer */
	for ( i = c_token+2; i < num_tokens; i++) {
	    if (equals(i,",") || equals(i,"]"))
		est_size++;
	    if (equals(i,"]"))
		break;
	}
	nsize = est_size;
    }
    if (nsize <= 0)
	int_error(c_token-1, "expecting array[size>0]");

    array->udv_value.v.value_array = gp_alloc((nsize+1) * sizeof(t_value), "array_command");
    array->udv_value.type = ARRAY;

    /* Element zero of the new array is not visible but contains the size */
    A = array->udv_value.v.value_array;
    A[0].v.int_val = nsize;
    for (i = 0; i <= nsize; i++) {
	A[i].type = NOTDEFINED;
    }

    /* Element zero can also hold an indicator that this is a colormap */
    /* FIXME: more sanity checks?  e.g. all entries INTGR */
    if (equals(c_token, "colormap")) {
	c_token++;
	if (nsize >= 2)	/* Need at least 2 entries to calculate range */
	    A[0].type = COLORMAP_ARRAY;
    }

    /* Initializer syntax:   array A[10] = [x,y,z,,"foo",] */
    if (equals(c_token, "=")) {
	int initializers = 0;
	if (!equals(++c_token, "["))
	    int_error(c_token, "expecting Array[size] = [x,y,...]");
	c_token++;
	for (i = 1; i <= nsize; i++) {
	    if (equals(c_token, "]"))
		break;
	    if (equals(c_token, ",")) {
		initializers++;
		c_token++;
		continue;
	    }
	    const_express(&A[i]);
	    initializers++;
	    if (equals(c_token, "]"))
		break;
	    if (equals(c_token, ","))
		c_token++;
	    else
		int_error(c_token, "expecting Array[size] = [x,y,...]");
	}
	c_token++;
	/* If the size is determined by the number of initializers */
	if (A[0].v.int_val == 0)
	    A[0].v.int_val = initializers;
    }

    return;
}


load_command()
{

    c_token++;
    if (equals(c_token, "$") && isletter(c_token+1) && !equals(c_token+2,"[")) {
	/* "load" a datablock rather than a file */
	/* datablock_name will eventually be freed by lf_pop() */
	char *datablock_name = gp_strdup(parse_datablock_name());
	load_file(NULL, datablock_name, 6);
    } else {
	/* These need to be local so that recursion works. */
	/* They will eventually be freed by lf_pop(). */
	FILE *fp;
	char *save_file = try_to_get_string();
	if (!save_file)
	    int_error(c_token, "expecting filename");
	gp_expand_tilde(&save_file);
	fp = strcmp(save_file, "-") ? loadpath_fopen(save_file, "r") : stdout;
	load_file(fp, save_file, 1);
    }
}


do_shell()
{
    screen_ok = FALSE;
    c_token++;

    if ((vaxc$errno = lib$spawn()) != SS$_NORMAL) {
	os_error(NO_CARET, "spawn error");
    }
}


undefine_command()
{
    char key[MAX_ID_LEN+1];
    TBOOLEAN wildcard;

    c_token++;               /* consume the command name */

    while (!END_OF_COMMAND) {
	/* copy next var name into key */
	copy_str(key, c_token, MAX_ID_LEN);

	/* Peek ahead - must do this, because a '*' is returned as a
	   separate token, not as part of the 'key' */
	wildcard = equals(c_token+1,"*");
	if (wildcard)
	    c_token++;

	/* The '$' starting a data block name is a separate token */
	else if (*key == '$')
	    copy_str(&key[1], ++c_token, MAX_ID_LEN-1);

	/* Other strange stuff on command line */
	else if (!isletter(c_token))
	    int_error(c_token, "Not a variable name");

	/* This command cannot deal with array elements or functions */
	if (equals(c_token+1, "[") || equals(c_token+1, "("))
	    int_error(c_token, "Cannot undefine function or array element");

	/* ignore internal variables */
	if (strncmp(key, "GPVAL_", 6) && strncmp(key, "MOUSE_", 6))
	    del_udv_by_name( key, wildcard );

	c_token++;
    }
}


if_command()
{
	if_else_command(IF_INITIAL);
	return;
}


iteration_early_exit()
{
    return (requested_continue || requested_break);
}


changedir(char *path)
{
#if defined(MSDOS)
    /* first deal with drive letter */

    if (isalpha((unsigned char)path[0]) && (path[1] == ':')) {
	int driveno = toupper((unsigned char)path[0]) - 'A';	/* 0=A, 1=B, ... */

# if defined(__EMX__) || defined(__WATCOMC__)
	(void) _chdrive(driveno + 1);
# elif defined(__DJGPP__)
	(void) setdisk(driveno);
# endif
	path += 2;		/* move past drive letter */
    }
    /* then change to actual directory */
    if (*path)
	if (chdir(path))
	    return 1;

    return 0;			/* should report error with setdrive also */

#elif defined(_WIN32)
    LPWSTR pathw = UnicodeText(path, encoding);
    int ret = !SetCurrentDirectoryW(pathw);
    free(pathw);
    return ret;
#elif defined(__EMX__) && defined(OS2)
    return _chdir2(path);
#else
    return chdir(path);
#endif /* MSDOS etc. */
}


help_command()
{
    while (!(END_OF_COMMAND))
	c_token++;
    fputs("This gnuplot was not built with inline help\n", stderr);
}


while_command()
{
    int do_start, do_end;
    char *clause;
    int save_token, end_token;
    double exprval;

    c_token++;
    save_token = c_token;
    exprval = real_expression();

    if (!equals(c_token,"{"))
	int_error(c_token,"expecting {while-clause}");
    end_token = find_clause(&do_start, &do_end);

    clause = new_clause(do_start, do_end);
    begin_clause();

    iteration_depth++;
    while (exprval != 0) {
	requested_continue = FALSE;
	do_string(clause);
	if (command_exit_requested)
	    requested_break = TRUE;
	if (requested_break)
	    break;
	c_token = save_token;
	exprval = real_expression();
    };
    iteration_depth--;

    end_clause();
    free(clause);
    c_token = end_token;
    requested_break = FALSE;
    requested_continue = FALSE;
}


read_line(const char *prompt, int start)
{
    TBOOLEAN more = FALSE;
    int last = 0;

    current_prompt = prompt;

    /* Once we start to read a new line, the tokens pointing into the old */
    /* line are no longer valid.  We used to _not_ clear things here, but */
    /* that lead to errors when a mouse-triggered replot request came in  */
    /* while a new line was being read.   Bug 3602388 Feb 2013.           */
    if (start == 0) {
	c_token = num_tokens = 0;
	gp_input_line[0] = '\0';
    }

    do {
	/* grab some input */
	if (gp_get_string(gp_input_line + start, gp_input_line_len - start,
		         ((more) ? ">" : prompt))
	    == (char *) NULL)
	{
	    /* end-of-file */
	    if (interactive)
		(void) putc('\n', stderr);
	    gp_input_line[start] = NUL;
	    inline_num++;
	    if (start > 0 && curly_brace_count == 0)	/* don't quit yet - process what we have */
		more = FALSE;
	    else
		return (1);	/* exit gnuplot */
	} else {
	    /* normal line input */
	    /* gp_input_line must be NUL-terminated for strlen not to pass the
	     * the bounds of this array */
	    last = strlen(gp_input_line) - 1;
	    if (last >= 0) {
		if (gp_input_line[last] == '\n') {	/* remove any newline */
		    gp_input_line[last] = NUL;
		    if (last > 0 && gp_input_line[last-1] == '\r')
			gp_input_line[--last] = NUL;
		    /* Watch out that we don't backup beyond 0 (1-1-1) */
		    if (last > 0)
			--last;
		} else if (last + 2 >= gp_input_line_len) {
		    extend_input_line();
		    /* read rest of line, don't print "> " */
		    start = last + 1;
		    more = TRUE;
		    continue;
		    /* else fall through to continuation handling */
		} /* if(grow buffer?) */
		if (gp_input_line[last] == '\\') {
		    /* line continuation */
		    start = last;
		    more = TRUE;
		} else
		    more = FALSE;
	    } else
		more = FALSE;
	}
    } while (more);
    return (0);
}


command()
{
    int i;

    for (i = 0; i < MAX_NUM_VAR; i++)
	c_dummy_var[i][0] = NUL;	/* no dummy variables */

    if (is_definition(c_token))
	define();
    else if (is_array_assignment())
	;
    else
	(*lookup_ftable(&command_ftbl[0],c_token))();

    return;
}


continue_command()
{
    c_token++;
    if (iteration_depth == 0)
	return;
    /* Skip to end of current clause */
    c_token = num_tokens;
    /* request that remainder of this iteration be skipped also */
    requested_continue = TRUE;
}


plot_command()
{
    plot_token = c_token++;
    plotted_data_from_stdin = FALSE;
    refresh_nplots = 0;
    SET_CURSOR_WAIT;
#ifdef USE_MOUSE
    plot_mode(MODE_PLOT);
    add_udv_by_name("MOUSE_X")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_Y")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_X2")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_Y2")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_BUTTON")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_SHIFT")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_ALT")->udv_value.type = NOTDEFINED;
    add_udv_by_name("MOUSE_CTRL")->udv_value.type = NOTDEFINED;
#endif
    plotrequest();
    /* Clear "hidden" flag for any plots that may have been toggled off */
    if (term->modify_plots)
	term->modify_plots(MODPLOTS_SET_VISIBLE, -1);
    SET_CURSOR_ARROW;
}


history_command()
{
#ifdef USE_READLINE
    c_token++;

    if (!END_OF_COMMAND && equals(c_token,"?")) {
	static char *search_str = NULL;  /* string from command line to search for */

	/* find and show the entries */
	c_token++;
	m_capture(&search_str, c_token, c_token);  /* reallocates memory */
	printf ("history ?%s\n", search_str);
	if (!history_find_all(search_str))
	    int_error(c_token,"not in history");
	c_token++;

    } else if (!END_OF_COMMAND && equals(c_token,"!")) {
	const char *line_to_do = NULL;  /* command returned by search	*/

	c_token++;
	if (isanumber(c_token)) {
	    int i = int_expression();
	    line_to_do = history_find_by_number(i);
	} else {
	    char *search_str = NULL;  /* string from command line to search for */
	    m_capture(&search_str, c_token, c_token);
	    line_to_do = history_find(search_str);
	    free(search_str);
	}
	if (line_to_do == NULL)
	    int_error(c_token, "not in history");

	/* Add the command to the history.
	   Note that history commands themselves are no longer added to the history. */
	add_history((char *) line_to_do);

	printf("  Executing:\n\t%s\n", line_to_do);
	do_string(line_to_do);
	c_token++;

    } else {
	int n = 0;		   /* print only <last> entries */
	char *tmp;
	TBOOLEAN append = FALSE;   /* rewrite output file or append it */
	static char *name = NULL;  /* name of the output file; NULL for stdout */

	TBOOLEAN quiet = history_quiet;
	if (!END_OF_COMMAND && almost_equals(c_token,"q$uiet")) {
	    /* option quiet to suppress history entry numbers */
	    quiet = TRUE;
	    c_token++;
	}
	/* show history entries */
	if (!END_OF_COMMAND && isanumber(c_token)) {
	    n = int_expression();
	}
	if ((tmp = try_to_get_string())) {
	    free(name);
	    name = tmp;
	    if (!END_OF_COMMAND && almost_equals(c_token, "ap$pend")) {
		append = TRUE;
		c_token++;
	    }
	}
	write_history_n(n, (quiet ? "" : name), (append ? "a" : "w"));
    }

#else
    c_token++;
    int_warn(NO_CARET, "This copy of gnuplot was built without support for command history.");
#endif /* defined(USE_READLINE) */
}


do_system(const char *cmd)
{
    int ierr;

/* (am, 19980929)
 * OS/2 related note: cmd.exe returns 255 if called w/o argument.
 * i.e. calling a shell by "!" will always end with an error message.
 * A workaround has to include checking for EMX,OS/2, two environment
 *  variables,...
 */
    if (!cmd)
	return;
    restrict_popen();
#if defined(_WIN32) && !defined(WGP_CONSOLE)
    /* Open a console so we can see the command's output */
    WinOpenConsole();
#endif
#if defined(_WIN32) && !defined(HAVE_BROKEN_WSYSTEM)
    {
	LPWSTR wcmd = UnicodeText(cmd, encoding);
	ierr = _wsystem(wcmd);
	free(wcmd);
    }
#else
    ierr = system(cmd);
#endif
    report_error(ierr);
}


system_command()
{
    char *cmd;
    ++c_token;
    cmd = try_to_get_string();
    do_system(cmd);
    free(cmd);
}


do_command()
{
    t_iterator *do_iterator;
    int do_start, do_end;
    int end_token;
    char *clause;

    c_token++;
    do_iterator = check_for_iteration();
    if (forever_iteration(do_iterator)) {
	cleanup_iteration(do_iterator);
	int_error(c_token-2, "unbounded iteration not accepted here");
    }

    if (!equals(c_token,"{")) {
	cleanup_iteration(do_iterator);
	int_error(c_token,"expecting {do-clause}");
    }
    end_token = find_clause(&do_start, &do_end);

    clause = new_clause(do_start, do_end);
    begin_clause();

    iteration_depth++;

    /* Sometimes the start point of a nested iteration is not within the
     * limits for all levels of nesting. In this case we need to advance
     * through the iteration to find the first good set of indices.
     * If we don't find one, forget the whole thing.
     */
    if (empty_iteration(do_iterator) && !next_iteration(do_iterator)) {
	strcpy(clause, ";");
    }

    do {
	requested_continue = FALSE;
	do_string(clause);

	if (command_exit_requested != 0)
	    requested_break = TRUE;
	if (requested_break)
	    break;
    } while (next_iteration(do_iterator));
    iteration_depth--;

    free(clause);
    end_clause();
    c_token = end_token;

    /* FIXME:  If any of the above exited via int_error() then this	*/
    /* cleanup never happens and we leak memory.  But do_iterator can	*/
    /* not be static or global because do_command() can recurse.	*/
    do_iterator = cleanup_iteration(do_iterator);
    requested_break = FALSE;
    requested_continue = FALSE;
}


com_line()
{
#ifdef OS2_IPC
static char *input_line_SharedMem = NULL;

    if (input_line_SharedMem == NULL) {  /* get shared mem only once */
    if (DosGetNamedSharedMem((PVOID) &input_line_SharedMem,
		mouseSharedMemName, PAG_WRITE | PAG_READ))
	fputs("readline.c: DosGetNamedSharedMem ERROR\n", stderr);
    else
	*input_line_SharedMem = 0;
    }
#endif /* OS2_IPC */

    if (multiplot) {
	/* calls int_error() if it is not happy */
	term_check_multiplot_okay(interactive);

	if (read_line("multiplot> ", 0))
	    return (1);
    } else {

#if defined(OS2_IPC) && defined(USE_MOUSE)
	ULONG u;
	if (thread_rl_Running == 0) {
	    int res = _beginthread(thread_read_line,NULL,32768,NULL);
	    if (res == -1)
		fputs("error command.c could not begin thread\n",stderr);
	}
	/* wait until a line is read or gnupmdrv makes shared mem available */
	DosWaitEventSem(semInputReady,SEM_INDEFINITE_WAIT);
	DosResetEventSem(semInputReady,&u);
	if (thread_rl_Running) {
	    if (input_line_SharedMem == NULL || !*input_line_SharedMem)
		return (0);
	    if (*input_line_SharedMem=='%') {
		do_event( (struct gp_event_t*)(input_line_SharedMem+1) ); /* pass terminal's event */
		input_line_SharedMem[0] = 0; /* discard the whole command line */
		thread_rl_RetCode = 0;
		return (0);
	    }
	    if (*input_line_SharedMem &&
		strstr(input_line_SharedMem,"plot") != NULL &&
		(strcmp(term->name,"pm") && strcmp(term->name,"x11"))) {
		/* avoid plotting if terminal is not PM or X11 */
		fprintf(stderr,"\n\tCommand(s) ignored for other than PM and X11 terminals\a\n");
		if (interactive) fputs(PROMPT,stderr);
		input_line_SharedMem[0] = 0; /* discard the whole command line */
		return (0);
	    }
	    strcpy(gp_input_line, input_line_SharedMem);
	    input_line_SharedMem[0] = 0;
	    thread_rl_RetCode = 0;
	}
	if (thread_rl_RetCode)
	    return (1);

#else	/* The normal case */
	if (read_line(PROMPT, 0))
	    return (1);
#endif	/* defined(OS2_IPC) && defined(USE_MOUSE) */
    }

    /* So we can flag any new output: if false at time of error,
     * we reprint the command line before printing caret.
     * TRUE for interactive terminals, since the command line is typed.
     * FALSE for non-terminal stdin, so command line is printed anyway.
     * (DFK 11/89)
     */
    screen_ok = interactive;

    if (do_line())
	return (1);
    else
	return (0);
}


replot_command()
{
    if (!*replot_line)
	int_error(c_token, "no previous plot");

    if (volatile_data && (refresh_ok != E_REFRESH_NOT_OK) && !replot_disabled) {
	FPRINTF((stderr,"volatile_data %d refresh_ok %d plotted_data_from_stdin %d\n",
		volatile_data, refresh_ok, plotted_data_from_stdin));
	refresh_command();
	return;
    }

    /* Disable replot for some reason; currently used by the mouse/hotkey
       capable terminals to avoid replotting when some data come from stdin,
       i.e. when  plotted_data_from_stdin==1  after plot "-".
    */
    if (replot_disabled) {
	replot_disabled = FALSE;
	bail_to_command_line(); /* be silent --- don't mess the screen */
    }
    if (!term) /* unknown terminal */
	int_error(c_token, "use 'set term' to set terminal type first");

    c_token++;
    SET_CURSOR_WAIT;
    if (term->flags & TERM_INIT_ON_REPLOT)
	term->init();
    replotrequest();
    SET_CURSOR_ARROW;
}


is_history_command(const char *line)
{
    int i;
    int start = 0;
    int length = 0;
    int after = 0;
    const char str[] = "hi$story";

    /* skip leading whitespace */
    while (isblank((unsigned char) line[start]))
	start++;

    /* find end of "token" */
    while ((line[start + length] != NUL) && !isblank((unsigned char) line[start + length]))
	length++;

    for (i = 0; i < length + after; i++) {
	if (str[i] != line[start + i]) {
	    if (str[i] != '$')
		return FALSE;
	    else {
		after = 1;
		start--;	/* back up token ptr */
	    }
	}
    }

    /* i now beyond end of token string */

    return (after || str[i] == '$' || str[i] == NUL);
}


toggle_command()
{
    int plotno = -1;
    char *plottitle = NULL;
    TBOOLEAN foundit = FALSE;

    c_token++;

    if (equals(c_token, "all")) {
	c_token++;

    } else if ((plottitle = try_to_get_string()) != NULL) {
	struct curve_points *plot;
	int length = strlen(plottitle);
	if (refresh_ok == E_REFRESH_OK_2D)
	    plot = first_plot;
	else if (refresh_ok == E_REFRESH_OK_3D)
	    plot = (struct curve_points *)first_3dplot;
	else
	    plot = NULL;
	for (plotno = 0; plot != NULL; plot = plot->next, plotno++) {
	    if (plot->title)
		if (!strcmp(plot->title, plottitle)
		||  (plottitle[length-1] == '*' && !strncmp(plot->title, plottitle, length-1))) {
		    foundit = TRUE;
		    break;
		}
	}
	free(plottitle);
	if (!foundit) {
	    int_warn(NO_CARET,"Did not find a plot with that title");
	    return;
	}

    } else {
	plotno = int_expression() - 1;
    }

    if (term->modify_plots)
	term->modify_plots(MODPLOTS_INVERT_VISIBILITIES, plotno);
}


Better error handling for faulty font syntax

A missing close-quote in an enhanced text font specification could
cause a segfault.
Bug #2303

lp_use_properties(struct lp_style_type *lp, int tag)
{
    /*  This function looks for a linestyle defined by 'tag' and copies
     *  its data into the structure 'lp'.
     */

    struct linestyle_def *this;
    int save_flags = lp->flags;

    this = first_linestyle;
    while (this != NULL) {
	if (this->tag == tag) {
	    *lp = this->lp_properties;
	    lp->flags = save_flags;
	    return;
	} else {
	    this = this->next;
	}
    }

    /* No user-defined style with this tag; fall back to default line type. */
    load_linetype(lp, tag);
}

com_line() in command.c in gnuplot 5.4 leads to an out-of-bounds-write from strncpy() that may lead to arbitrary code execution.


on_page(int x, int y)
{
    if (term->flags & TERM_CAN_CLIP)
	return TRUE;

    if ((0 < x && x < term->xmax) && (0 < y && y < term->ymax))
	return TRUE;

    return FALSE;
}


graphics_null()
{
    fprintf(stderr,
	    "WARNING: Plotting with an 'unknown' terminal.\n"
	    "No output will be generated. Please select a terminal with 'set terminal'.\n");
}


ignore_enhanced(TBOOLEAN flag)
{
    ignore_enhanced_text = flag;
}


term_end_multiplot()
{
    FPRINTF((stderr, "term_end_multiplot()\n"));
    if (!multiplot)
	return;

    if (term_suspended) {
	if (term->resume)
	    (*term->resume) ();
	term_suspended = FALSE;
    }

    multiplot_end();

    term_end_plot();
#ifdef USE_MOUSE
    UpdateStatusline();
#endif
}


escape_reserved_chars(const char *str, const char *reserved)
{
    int i;
    char *escaped_str;
    int newsize = strlen(str);

	/* Count number of reserved characters */
	for (i = 0; str[i] != '\0'; i++) {
	    if (strchr(reserved, str[i]))
		newsize++;
	}
	escaped_str = gp_alloc(newsize + 1, NULL);

	/* Prefix each reserved character with a backslash */
	for (i = 0, newsize = 0; str[i] != '\0'; i++) {
	    if (strchr(reserved, str[i]))
		escaped_str[newsize++] = '\\';
	    escaped_str[newsize++] = str[i];
	}
	escaped_str[newsize] = '\0';

    return escaped_str;
}


init_terminal()
{
    char *term_name = DEFAULTTERM;
#if defined(__BEOS__) || defined(X11)
    char *env_term = NULL;      /* from TERM environment var */
#endif
#ifdef X11
    char *display = NULL;
#endif
    char *gnuterm = NULL;

    /* GNUTERM environment variable is primary */
    gnuterm = getenv("GNUTERM");
    if (gnuterm != (char *) NULL) {
	/* April 2017 - allow GNUTERM to include terminal options */
	char *set_term = "set term ";
	char *set_term_command = gp_alloc(strlen(set_term) + strlen(gnuterm) + 4, NULL);
	strcpy(set_term_command, set_term);
	strcat(set_term_command, gnuterm);
	do_string(set_term_command);
	free(set_term_command);
	/* replicate environmental variable GNUTERM for internal use */
	Gstring(&(add_udv_by_name("GNUTERM")->udv_value), gp_strdup(gnuterm));
	return;

    } else {

#ifdef VMS
	term_name = vms_init();
#endif /* VMS */

	if (term_name == (char *) NULL
            && getenv ("DOMTERM") != NULL)
          term_name = "domterm";

#ifdef __BEOS__
	env_term = getenv("TERM");
	if (term_name == (char *) NULL
	    && env_term != (char *) NULL && strcmp(env_term, "beterm") == 0)
	    term_name = "be";
#endif /* BeOS */

#ifdef QTTERM
	if (term_name == (char *) NULL)
	    term_name = "qt";
#endif

#ifdef WXWIDGETS
	if (term_name == (char *) NULL)
	    term_name = "wxt";
#endif

#ifdef _WIN32
	if (term_name == (char *) NULL)
	    term_name = "win";
#endif /* _WIN32 */

#if defined(__APPLE__) && defined(__MACH__) && defined(HAVE_FRAMEWORK_AQUATERM)
	/* Mac OS X with AquaTerm installed */
	term_name = "aqua";
#endif

#ifdef X11
	env_term = getenv("TERM");      /* try $TERM */
	if (term_name == (char *) NULL
	    && env_term != (char *) NULL && strcmp(env_term, "xterm") == 0)
	    term_name = "x11";
	display = getenv("DISPLAY");
	if (term_name == (char *) NULL && display != (char *) NULL)
	    term_name = "x11";
	if (X11_Display)
	    term_name = "x11";
#endif /* x11 */

#ifdef DJGPP
	term_name = "svga";
#endif

#ifdef GRASS
	term_name = "grass";
#endif

#ifdef OS2
/* amai: Note that we do some checks above and now overwrite any
   results. Perhaps we may disable checks above!? */
#ifdef X11
/* WINDOWID is set in sessions like xterm, etc.
   DISPLAY is also mandatory. */
	env_term = getenv("WINDOWID");
	display  = getenv("DISPLAY");
	if ((env_term != (char *) NULL) && (display != (char *) NULL))
	    term_name = "x11";
	else
#endif          /* X11 */
	    term_name = "pm";
#endif /*OS2 */

    }

    /* We have a name, try to set term type */
    if (term_name != NULL && *term_name != '\0') {
	int namelength = strlen(term_name);
	struct udvt_entry *name = add_udv_by_name("GNUTERM");

	Gstring(&name->udv_value, gp_strdup(term_name));

	if (strchr(term_name,' '))
	    namelength = strchr(term_name,' ') - term_name;

	/* Force the terminal to initialize default fonts, etc.	This prevents */
	/* segfaults and other strangeness if you set GNUTERM to "post" or    */
	/* "png" for example. However, calling X11_options() is expensive due */
	/* to the fork+execute of gnuplot_x11 and x11 can tolerate not being  */
	/* initialized until later.                                           */
	/* Note that gp_input_line[] is blank at this point.	              */
	if (change_term(term_name, namelength)) {
	    if (strcmp(term->name,"x11"))
		term->options();
	    return;
	}
	fprintf(stderr, "Unknown or ambiguous terminal name '%s'\n", term_name);
    }
    change_term("unknown", 7);
}


term_start_multiplot()
{
    FPRINTF((stderr, "term_start_multiplot()\n"));
    multiplot_start();
#ifdef USE_MOUSE
    UpdateStatusline();
#endif
}


stylefont(const char *fontname, TBOOLEAN isbold, TBOOLEAN isitalic)
{
    int div;
    char *markup = gp_alloc( strlen(fontname) + 16, "font markup");
    strcpy(markup, fontname);
    /* base font name can be followed by ,<size> or :Variant */
    if ((div = strcspn(markup,",:")))
	markup[div] = '\0';
    if (isbold)
	strcat(markup, ":Bold");
    if (isitalic)
	strcat(markup, ":Italic");

    FPRINTF((stderr, "MARKUP FONT: %s -> %s\n", fontname, markup));
    return markup;
}


style_from_fill(struct fill_style_type *fs)
{
    int fillpar, style;

    switch( fs->fillstyle ) {
    case FS_SOLID:
    case FS_TRANSPARENT_SOLID:
	fillpar = fs->filldensity;
	style = ((fillpar & 0xfff) << 4) + fs->fillstyle;
	break;
    case FS_PATTERN:
    case FS_TRANSPARENT_PATTERN:
	fillpar = fs->fillpattern;
	style = ((fillpar & 0xfff) << 4) + fs->fillstyle;
	break;
    case FS_EMPTY:
    default:
	/* solid fill with background color */
	style = FS_EMPTY;
	break;
    }

    return style;
}


closepath()
{
    if (term->path)
	(*term->path)(1);
}


line_and_point(unsigned int x, unsigned int y, int number)
{
    /* temporary(?) kludge to allow terminals with bad linetypes
       to make nice marks */

    (*term->linetype) (NICE_LINE);
    do_point(x, y, number);
}


term_suspend()
{
    FPRINTF((stderr, "term_suspend()\n"));
    if (term_initialised && !term_suspended && term->suspend) {
	FPRINTF((stderr, "- calling term->suspend()\n"));
	(*term->suspend) ();
	term_suspended = TRUE;
    }
}


list_terms()
{
    int i;
    char *line_buffer = gp_alloc(BUFSIZ, "list_terms");
    int sort_idxs[TERMCOUNT];

    /* sort terminal types alphabetically */
    for( i = 0; i < TERMCOUNT; i++ )
	sort_idxs[i] = i;
    qsort( sort_idxs, TERMCOUNT, sizeof(int), termcomp );
    /* now sort_idxs[] contains the sorted indices */

    StartOutput();
    strcpy(line_buffer, "\nAvailable terminal types:\n");
    OutLine(line_buffer);

    for (i = 0; i < TERMCOUNT; i++) {
	sprintf(line_buffer, "  %15s  %s\n",
		term_tbl[sort_idxs[i]].name,
		term_tbl[sort_idxs[i]].description);
	OutLine(line_buffer);
    }

    EndOutput();
    free(line_buffer);
}


strlen_tex(const char *str)
{
    const char *s = str;
    int len = 0;

    if (!strpbrk(s, "{}$[]\\")) {
	len = strlen(s);
	FPRINTF((stderr,"strlen_tex(\"%s\") = %d\n",s,len));
	return len;
    }

    while (*s) {
	switch (*s) {
	case '[':
		while (*s && *s != ']') s++;
		if (*s) s++;
		break;
	case '\\':
		s++;
		while (*s && isalpha((unsigned char)*s)) s++;
		len++;
		break;
	case '{':
	case '}':
	case '$':
	case '_':
	case '^':
		s++;
		break;
	default:
		s++;
		len++;
	}
    }

    FPRINTF((stderr,"strlen_tex(\"%s\") = %d\n",str,len));
    return len;
}


null_linewidth(double s)
{
    (void) s;                   /* avoid -Wunused warning */
}


term_apply_lp_properties(struct lp_style_type *lp)
{
    /*  This function passes all the line and point properties to the
     *  terminal driver and issues the corresponding commands.
     *
     *  Alas, sometimes it might be necessary to give some help to
     *  this function by explicitly issuing additional '(*term)(...)'
     *  commands.
     */
    int lt = lp->l_type;
    int dt = lp->d_type;
    t_dashtype custom_dash_pattern = lp->custom_dash_pattern;
    t_colorspec colorspec = lp->pm3d_color;

    if ((lp->flags & LP_SHOW_POINTS)) {
	/* change points, too
	 * Currently, there is no 'pointtype' function.  For points
	 * there is a special function also dealing with (x,y) co-
	 * ordinates.
	 */
	if (lp->p_size < 0)
	    (*term->pointsize) (pointsize);
	else
	    (*term->pointsize) (lp->p_size);
    }
    /*  _first_ set the line width, _then_ set the line type !
     *  The linetype might depend on the linewidth in some terminals.
     */
    (*term->linewidth) (lp->l_width);

    /* LT_DEFAULT (used only by "set errorbars"?) means don't change it */
    if (lt == LT_DEFAULT)
	;
    else

    /* The paradigm for handling linetype and dashtype in version 5 is */
    /* linetype < 0 (e.g. LT_BACKGROUND, LT_NODRAW) means some special */
    /* category that will be handled directly by term->linetype().     */
    /* linetype > 0 is now redundant. It used to encode both a color   */
    /* and a dash pattern.  Now we have separate mechanisms for those. */
    if (LT_COLORFROMCOLUMN < lt && lt < 0)
	(*term->linetype) (lt);
    else if (term->set_color == null_set_color) {
	(*term->linetype) (lt-1);
	return;
    } else /* All normal lines will be solid unless a dashtype is given */
	(*term->linetype) (LT_SOLID);

    /* Version 5.3
     * If the line is not wanted at all, setting dashtype and color can only hurt
     */
    if (lt == LT_NODRAW)
	return;

    /* Apply dashtype or user-specified dash pattern, which may override  */
    /* the terminal-specific dot/dash pattern belonging to this linetype. */
    if (lt == LT_AXIS)
	; /* LT_AXIS is a special linetype that may incorporate a dash pattern */
    else if (dt == DASHTYPE_CUSTOM)
	(*term->dashtype) (dt, &custom_dash_pattern);
    else if (dt == DASHTYPE_SOLID)
	(*term->dashtype) (dt, NULL);
    else if (dt >= 0)
	/* The null_dashtype() routine or a version 5 terminal's private  */
	/* dashtype routine converts this into a call to term->linetype() */
	/* yielding the same result as in version 4 except possibly for a */
	/* different line width.					  */
	(*term->dashtype) (dt, NULL);

    /* Finally adjust the color of the line */
    apply_pm3dcolor(&colorspec);
}


null_justify_text(enum JUSTIFY just)
{
    return (just == LEFT);
}


termcomp(const generic *arga, const generic *argb)
{
    const int *a = arga;
    const int *b = argb;

    return( strcasecmp( term_tbl[*a].name, term_tbl[*b].name ) );
}


test_term()
{
    struct termentry *t = term;
    const char *str;
    int x, y, xl, yl, i;
    int xmax_t, ymax_t, x0, y0;
    char label[MAX_ID_LEN];
    int key_entry_height;
    int p_width;
    TBOOLEAN already_in_enhanced_text_mode;
    static t_colorspec black = BLACK_COLORSPEC;

    already_in_enhanced_text_mode = t->flags & TERM_ENHANCED_TEXT;
    if (!already_in_enhanced_text_mode)
	do_string("set termopt enh");

    term_start_plot();
    screen_ok = FALSE;
    xmax_t = (t->xmax * xsize);
    ymax_t = (t->ymax * ysize);
    x0 = (xoffset * t->xmax);
    y0 = (yoffset * t->ymax);

    p_width = pointsize * t->h_tic;
    key_entry_height = pointsize * t->v_tic * 1.25;
    if (key_entry_height < t->v_char)
	key_entry_height = t->v_char;

    /* Sync point for epslatex text positioning */
    (*t->layer)(TERM_LAYER_FRONTTEXT);

    /* border linetype */
    (*t->linewidth) (1.0);
    (*t->linetype) (LT_BLACK);
    newpath();
    (*t->move) (x0, y0);
    (*t->vector) (x0 + xmax_t - 1, y0);
    (*t->vector) (x0 + xmax_t - 1, y0 + ymax_t - 1);
    (*t->vector) (x0, y0 + ymax_t - 1);
    (*t->vector) (x0, y0);
    closepath();

    /* Echo back the current terminal type */
    if (!strcmp(term->name,"unknown"))
	int_error(NO_CARET, "terminal type is unknown");
    else {
	char tbuf[64];
	(void) (*t->justify_text) (LEFT);
	sprintf(tbuf,"%s  terminal test", term->name);
	(*t->put_text) (x0 + t->h_char * 2, y0 + ymax_t - t->v_char, tbuf);
	sprintf(tbuf, "gnuplot version %s.%s  ", gnuplot_version, gnuplot_patchlevel);
	(*t->put_text) (x0 + t->h_char * 2, y0 + ymax_t - t->v_char * 2.25, tbuf);
    }

    (*t->linetype) (LT_AXIS);
    (*t->move) (x0 + xmax_t / 2, y0);
    (*t->vector) (x0 + xmax_t / 2, y0 + ymax_t - 1);
    (*t->move) (x0, y0 + ymax_t / 2);
    (*t->vector) (x0 + xmax_t - 1, y0 + ymax_t / 2);

    /* How well can we estimate width and height of characters?
     * Textbox fill shows true size, surrounding box shows the generic estimate
     * used to reserve space during plot layout.
     */
    if (TRUE) {
	struct text_label sample = EMPTY_LABELSTRUCT;
	struct textbox_style save_opts = textbox_opts[0];
	textbox_style *textbox = &textbox_opts[0];
	sample.text = "12345678901234567890";
	sample.pos = CENTRE;
	sample.boxed = -1;
	textbox->opaque = TRUE;
	textbox->noborder = TRUE;
	textbox->fillcolor.type = TC_RGB;
	textbox->fillcolor.lt = 0xccccee;
	/* disable extra space around text */
	textbox->xmargin = 0;
	textbox->ymargin = 0;

	(*t->linetype) (LT_SOLID);
	write_label(xmax_t/2, ymax_t/2, &sample);
	textbox_opts[0] = save_opts;

	sample.boxed = 0;
	sample.text = "true vs. estimated text dimensions";
	write_label(xmax_t/2, ymax_t/2 + 1.5 * t->v_char, &sample);

	newpath();
	(*t->move) (x0 + xmax_t / 2 - t->h_char * 10, y0 + ymax_t / 2 + t->v_char / 2);
	(*t->vector) (x0 + xmax_t / 2 + t->h_char * 10, y0 + ymax_t / 2 + t->v_char / 2);
	(*t->vector) (x0 + xmax_t / 2 + t->h_char * 10, y0 + ymax_t / 2 - t->v_char / 2);
	(*t->vector) (x0 + xmax_t / 2 - t->h_char * 10, y0 + ymax_t / 2 - t->v_char / 2);
	(*t->vector) (x0 + xmax_t / 2 - t->h_char * 10, y0 + ymax_t / 2 + t->v_char / 2);
	closepath();
    }

    /* Test for enhanced text */
    (*t->linetype) (LT_BLACK);
    if (t->flags & TERM_ENHANCED_TEXT) {
	char *tmptext1 =   "Enhanced text:   {x@_{0}^{n+1}}";
	char *tmptext2 = "&{Enhanced text:  }{/:Bold Bold}{/:Italic  Italic}";
	(*t->put_text) (x0 + xmax_t * 0.5, y0 + ymax_t * 0.40, tmptext1);
	(*t->put_text) (x0 + xmax_t * 0.5, y0 + ymax_t * 0.35, tmptext2);
	(*t->set_font)("");
	if (!already_in_enhanced_text_mode)
	    do_string("set termopt noenh");
    }

    /* test justification */
    (void) (*t->justify_text) (LEFT);
    (*t->put_text) (x0 + xmax_t / 2, y0 + ymax_t / 2 + t->v_char * 6, "left justified");
    str = "centre+d text";
    if ((*t->justify_text) (CENTRE))
	(*t->put_text) (x0 + xmax_t / 2,
			y0 + ymax_t / 2 + t->v_char * 5, str);
    else
	(*t->put_text) (x0 + xmax_t / 2 - strlen(str) * t->h_char / 2,
			y0 + ymax_t / 2 + t->v_char * 5, str);
    str = "right justified";
    if ((*t->justify_text) (RIGHT))
	(*t->put_text) (x0 + xmax_t / 2,
			y0 + ymax_t / 2 + t->v_char * 4, str);
    else
	(*t->put_text) (x0 + xmax_t / 2 - strlen(str) * t->h_char,
			y0 + ymax_t / 2 + t->v_char * 4, str);

    /* test tic size */
    (*t->linetype)(2);
    (*t->move) ((unsigned int) (x0 + xmax_t / 2 + t->h_tic * (1 + axis_array[FIRST_X_AXIS].ticscale)), y0 + (unsigned int) ymax_t - 1);
    (*t->vector) ((unsigned int) (x0 + xmax_t / 2 + t->h_tic * (1 + axis_array[FIRST_X_AXIS].ticscale)),
		  (unsigned int) (y0 + ymax_t - axis_array[FIRST_X_AXIS].ticscale * t->v_tic));
    (*t->move) ((unsigned int) (x0 + xmax_t / 2), y0 + (unsigned int) (ymax_t - t->v_tic * (1 + axis_array[FIRST_X_AXIS].ticscale)));
    (*t->vector) ((unsigned int) (x0 + xmax_t / 2 + axis_array[FIRST_X_AXIS].ticscale * t->h_tic),
		  (unsigned int) (y0 + ymax_t - t->v_tic * (1 + axis_array[FIRST_X_AXIS].ticscale)));
    (void) (*t->justify_text) (RIGHT);
    (*t->put_text) (x0 + (unsigned int) (xmax_t / 2 - 1* t->h_char),
		    y0 + (unsigned int) (ymax_t - t->v_char),
		    "show ticscale");
    (void) (*t->justify_text) (LEFT);
    (*t->linetype)(LT_BLACK);

    /* test line and point types */
    x = x0 + xmax_t - t->h_char * 7 - p_width;
    y = y0 + ymax_t - key_entry_height;
    (*t->pointsize) (pointsize);
    for (i = -2; y > y0 + key_entry_height; i++) {
	struct lp_style_type ls = DEFAULT_LP_STYLE_TYPE;
	ls.l_width = 1;
	load_linetype(&ls,i+1);
	term_apply_lp_properties(&ls);

	(void) sprintf(label, "%d", i + 1);
	if ((*t->justify_text) (RIGHT))
	    (*t->put_text) (x, y, label);
	else
	    (*t->put_text) (x - strlen(label) * t->h_char, y, label);
	(*t->move) (x + t->h_char, y);
	(*t->vector) (x + t->h_char * 5, y);
	if (i >= -1)
	    (*t->point) (x + t->h_char * 6 + p_width / 2, y, i);
	y -= key_entry_height;
    }

    /* test arrows (should line up with rotated text) */
    (*t->linewidth) (1.0);
    (*t->linetype) (0);
    (*t->dashtype) (DASHTYPE_SOLID, NULL);
    x = x0 + 2. * t->v_char;
    y = y0 + ymax_t/2;
    xl = t->h_tic * 7;
    yl = t->v_tic * 7;
    i = curr_arrow_headfilled;
    curr_arrow_headfilled = AS_NOBORDER;
    (*t->arrow) (x, y-yl, x, y+yl, BOTH_HEADS);
    curr_arrow_headfilled = AS_EMPTY;
    (*t->arrow) (x, y, x + xl, y + yl, END_HEAD);
    curr_arrow_headfilled = AS_NOFILL;
    (*t->arrow) (x, y, x + xl, y - yl, END_HEAD);
    curr_arrow_headfilled = i;

    /* test text angle (should match arrows) */
    (*t->linetype)(0);
    str = "rotated ce+ntred text";
    if ((*t->text_angle) (TEXT_VERTICAL)) {
	if ((*t->justify_text) (CENTRE))
	    (*t->put_text) (x0 + t->v_char,
			    y0 + ymax_t / 2, str);
	else
	    (*t->put_text) (x0 + t->v_char,
			    y0 + ymax_t / 2 - strlen(str) * t->h_char / 2, str);
	(*t->justify_text) (LEFT);
	str = "  rotate by +45";
	(*t->text_angle)(45);
	(*t->put_text)(x0 + t->v_char * 3, y0 + ymax_t / 2, str);
	(*t->justify_text) (LEFT);
	str = "  rotate by -45";
	(*t->text_angle)(-45);
	(*t->put_text)(x0 + t->v_char * 3, y0 + ymax_t / 2, str);
    } else {
	(void) (*t->justify_text) (LEFT);
	(*t->put_text) (x0 + t->h_char * 2, y0 + ymax_t / 2, "cannot rotate text");
    }
    (void) (*t->justify_text) (LEFT);
    (void) (*t->text_angle) (0);

    /* test line widths */
    (void) (*t->justify_text) (LEFT);
    xl = xmax_t / 10;
    yl = ymax_t / 25;
    x = x0 + xmax_t * .075;
    y = y0 + yl;

    for (i=1; i<7; i++) {
	(*t->linewidth) ((double)(i)); (*t->linetype)(LT_BLACK);
	(*t->move) (x, y); (*t->vector) (x+xl, y);
	sprintf(label,"  lw %1d", i);
	(*t->put_text) (x+xl, y, label);
	y += yl;
    }
    (*t->put_text) (x, y, "linewidth");

    /* test native dashtypes (_not_ the 'set mono' sequence) */
    (void) (*t->justify_text) (LEFT);
    xl = xmax_t / 10;
    yl = ymax_t / 25;
    x = x0 + xmax_t * .3;
    y = y0 + yl;

    for (i=0; i<5; i++) {
 	(*t->linewidth) (1.0);
	(*t->linetype) (LT_SOLID);
	(*t->dashtype) (i, NULL);
	(*t->set_color)(&black);
	(*t->move) (x, y); (*t->vector) (x+xl, y);
	sprintf(label,"  dt %1d", i+1);
	(*t->put_text) (x+xl, y, label);
	y += yl;
    }
    (*t->put_text) (x, y, "dashtype");

    /* test fill patterns */
    x = x0 + xmax_t * 0.5;
    y = y0;
    xl = xmax_t / 40;
    yl = ymax_t / 8;
    (*t->linewidth) (1.0);
    (*t->linetype)(LT_BLACK);
    (*t->justify_text) (CENTRE);
    (*t->put_text)(x+xl*7, y + yl+t->v_char*1.5, "pattern fill");
    for (i=0; i<9; i++) {
	int style = ((i<<4) + FS_PATTERN);
	if (t->fillbox)
	    (*t->fillbox) ( style, x, y, xl, yl );
	newpath();
	(*t->move)  (x,y);
	(*t->vector)(x,y+yl);
	(*t->vector)(x+xl,y+yl);
	(*t->vector)(x+xl,y);
	(*t->vector)(x,y);
	closepath();
	sprintf(label, "%2d", i);
	(*t->put_text)(x+xl/2, y+yl+t->v_char*0.5, label);
	x += xl * 1.5;
    }

    {
	int cen_x = x0 + (int)(0.70 * xmax_t);
	int cen_y = y0 + (int)(0.83 * ymax_t);
	int radius = xmax_t / 20;

	/* test pm3d -- filled_polygon(), but not set_color() */
	if (t->filled_polygon) {
	    int i, j;
#define NUMBER_OF_VERTICES 6
	    int n = NUMBER_OF_VERTICES;
	    gpiPoint corners[NUMBER_OF_VERTICES+1];
#undef  NUMBER_OF_VERTICES

	    for (j=0; j<=1; j++) {
		int ix = cen_x + j*radius;
		int iy = cen_y - j*radius/2;
		for (i = 0; i < n; i++) {
		    corners[i].x = ix + radius * cos(2*M_PI*i/n);
		    corners[i].y = iy + radius * sin(2*M_PI*i/n);
		}
		corners[n].x = corners[0].x;
		corners[n].y = corners[0].y;
		if (j == 0) {
		    (*t->linetype)(2);
		    corners->style = FS_OPAQUE;
		} else {
		    (*t->linetype)(1);
		    corners->style = FS_TRANSPARENT_SOLID + (50<<4);
		}
		term->filled_polygon(n+1, corners);
	    }
	    str = "filled polygons:";
	} else
	    str = "No filled polygons";
	(*t->linetype)(LT_BLACK);
	i = ((*t->justify_text) (CENTRE)) ? 0 : t->h_char * strlen(str) / 2;
	(*t->put_text) (cen_x - i, cen_y + radius + t->v_char * 0.5, str);
    }

    term_end_plot();
}


write_multiline(
    int x, int y,
    char *text,
    JUSTIFY hor,                /* horizontal ... */
    VERT_JUSTIFY vert,          /* ... and vertical just - text in hor direction despite angle */
    int angle,                  /* assume term has already been set for this */
    const char *font)           /* NULL or "" means use default */
{
    struct termentry *t = term;
    char *p = text;

    if (!p)
	return;

    /* EAM 9-Feb-2003 - Set font before calculating sizes */
    if (font && *font)
	(*t->set_font) (font);

    if (vert != JUST_TOP) {
	/* count lines and adjust y */
	int lines = 0;          /* number of linefeeds - one fewer than lines */
	while (*p) {
	    if (*p++ == '\n')
		++lines;
	}
	if (angle)
	    x -= (vert * lines * t->v_char) / 2;
	else
	    y += (vert * lines * t->v_char) / 2;
    }

    for (;;) {                  /* we will explicitly break out */

	if ((text != NULL) && (p = strchr(text, '\n')) != NULL)
	    *p = 0;             /* terminate the string */

	if ((*t->justify_text) (hor)) {
	    if (on_page(x, y))
		(*t->put_text) (x, y, text);
	} else {
	    int len = estimate_strlen(text, NULL);
	    int hfix, vfix;

	    if (angle == 0) {
		hfix = hor * t->h_char * len / 2;
		vfix = 0;
	    } else {
		/* Attention: This relies on the numeric values of enum JUSTIFY! */
		hfix = hor * t->h_char * len * cos(angle * DEG2RAD) / 2 + 0.5;
		vfix = hor * t->v_char * len * sin(angle * DEG2RAD) / 2 + 0.5;
	    }
		if (on_page(x - hfix, y - vfix))
		    (*t->put_text) (x - hfix, y - vfix, text);
	}
	if (angle == 90 || angle == TEXT_VERTICAL)
	    x += t->v_char;
	else if (angle == -90 || angle == -TEXT_VERTICAL)
	    x -= t->v_char;
	else
	    y -= t->v_char;

	if (!p)
	    break;
	else {
	    /* put it back */
	    *p = '\n';
	}

	text = p + 1;
    }                           /* unconditional branch back to the for(;;) - just a goto ! */

    if (font && *font)
	(*t->set_font) ("");

}


null_dashtype(int type, t_dashtype *custom_dash_pattern)
{
    (void) custom_dash_pattern;	/* ignore */
    /*
     * If the terminal does not support user-defined dashtypes all we can do
     * do is fall through to the old (pre-v5) assumption that the dashtype,
     * if any, is part of the linetype.  We also assume that the color will
     * be adjusted after this.
     */
    if (type <= 0)
	type = LT_SOLID;
    term->linetype(type);
}


estimate_strlen(const char *text, double *height)
{
    int len;
    char *s;
    double estimated_fontheight = 1.0;

    if ((term->flags & TERM_IS_LATEX))
	return strlen_tex(text);

#ifdef GP_ENH_EST
    if (strchr(text,'\n') || (term->flags & TERM_ENHANCED_TEXT)) {
	struct termentry *tsave = term;
	term = &ENHest;
	term->put_text(0,0,text);
	len = term->xmax;
	estimated_fontheight = term->ymax / 10.;
	term = tsave;
	/* Assume that unicode escape sequences  \U+xxxx will generate a single character */
	/* ENHest_plaintext is filled in by the put_text() call to estimate.trm           */
	s = ENHest_plaintext;
	while ((s = contains_unicode(s)) != NULL) {
	    len -= 6;
	    s += 6;
	}
	FPRINTF((stderr,"Estimating length %d height %g for enhanced text \"%s\"",
		len, estimated_fontheight, text));
	FPRINTF((stderr,"  plain text \"%s\"\n", ENHest_plaintext));
    } else if (encoding == S_ENC_UTF8)
	len = strwidth_utf8(text);
    else
#endif
	len = strlen(text);

    if (height)
	*height = estimated_fontheight;

    return len;
}


parse_term_size( float *xsize, float *ysize, size_units default_units )
{
    size_units units = default_units;

    if (END_OF_COMMAND)
	int_error(c_token, "size requires two numbers:  xsize, ysize");
    *xsize = real_expression();
    if (almost_equals(c_token,"in$ches")) {
	c_token++;
	units = INCHES;
    } else if (equals(c_token,"cm")) {
	c_token++;
	units = CM;
    }
    switch (units) {
    case INCHES:	*xsize *= gp_resolution; break;
    case CM:		*xsize *= (float)gp_resolution / 2.54; break;
    case PIXELS:
    default:		 break;
    }

    if (!equals(c_token++,","))
	int_error(c_token, "size requires two numbers:  xsize, ysize");
    *ysize = real_expression();
    if (almost_equals(c_token,"in$ches")) {
	c_token++;
	units = INCHES;
    } else if (equals(c_token,"cm")) {
	c_token++;
	units = CM;
    }
    switch (units) {
    case INCHES:	*ysize *= gp_resolution; break;
    case CM:		*ysize *= (float)gp_resolution / 2.54; break;
    case PIXELS:
    default:		 break;
    }

    if (*xsize < 1 || *ysize < 1)
	int_error(c_token, "size: out of range");

    return units;
}


term_set_output(char *dest)
{
    FILE *f = NULL;

    FPRINTF((stderr, "term_set_output\n"));
    assert(dest == NULL || dest != outstr);

    if (multiplot) {
	fputs("In multiplot mode you can't change the output\n", stderr);
	return;
    }
    if (term && term_initialised) {
	(*term->reset) ();
	term_initialised = FALSE;
	/* switch off output to special postscript file (if used) */
	gppsfile = NULL;
    }
    if (dest == NULL) {         /* stdout */
	term_close_output();
    } else {

#if defined(PIPES)
	if (*dest == '|') {
	    restrict_popen();
#if defined(_WIN32 ) || defined(MSDOS)
	    if (term && (term->flags & TERM_BINARY))
		f = popen(dest + 1, "wb");
	    else
		f = popen(dest + 1, "w");
#else
	    f = popen(dest + 1, "w");
#endif
	    if (f == (FILE *) NULL)
		os_error(c_token, "cannot create pipe; output not changed");
	    else
		output_pipe_open = TRUE;
	} else {
#endif /* PIPES */

#ifdef _WIN32
	if (outstr && stricmp(outstr, "PRN") == 0) {
	    /* we can't call open_printer() while printer is open, so */
	    close_printer(gpoutfile);   /* close printer immediately if open */
	    gpoutfile = stdout; /* and reset output to stdout */
	    free(outstr);
	    outstr = NULL;
	}
	if (stricmp(dest, "PRN") == 0) {
	    if ((f = open_printer()) == (FILE *) NULL)
		os_error(c_token, "cannot open printer temporary file; output may have changed");
	} else
#endif

	{
#if defined (MSDOS)
	    if (outstr && (0 == stricmp(outstr, dest))) {
		/* On MSDOS, you cannot open the same file twice and
		 * then close the first-opened one and keep the second
		 * open, it seems. If you do, you get lost clusters
		 * (connection to the first version of the file is
		 * lost, it seems). */
		/* FIXME: this is not yet safe enough. You can fool it by
		 * specifying the same output file in two different ways
		 * (relative vs. absolute path to file, e.g.) */
		term_close_output();
	    }
#endif
	    if (term && (term->flags & TERM_BINARY))
		f = FOPEN_BINARY(dest);
	    else
		f = fopen(dest, "w");

	    if (f == (FILE *) NULL)
		os_error(c_token, "cannot open file; output not changed");
	}
#if defined(PIPES)
	}
#endif

	term_close_output();
	gpoutfile = f;
	outstr = dest;
	opened_binary = (term && (term->flags & TERM_BINARY));
    }
}


enhanced_recursion(
    const char *p,
    TBOOLEAN brace,
    char *fontname,
    double fontsize,
    double base,
    TBOOLEAN widthflag,
    TBOOLEAN showflag,
    int overprint)
{
    TBOOLEAN wasitalic, wasbold;

    /* Keep track of the style of the font passed in at this recursion level */
    wasitalic = (strstr(fontname, ":Italic") != NULL);
    wasbold = (strstr(fontname, ":Bold") != NULL);

    FPRINTF((stderr, "RECURSE WITH \"%s\", %d %s %.1f %.1f %d %d %d",
		p, brace, fontname, fontsize, base, widthflag, showflag, overprint));

    /* Start each recursion with a clean string */
    (term->enhanced_flush)();

    if (base + fontsize > enhanced_max_height) {
	enhanced_max_height = base + fontsize;
	ENH_DEBUG(("Setting max height to %.1f\n", enhanced_max_height));
    }

    if (base < enhanced_min_height) {
	enhanced_min_height = base;
	ENH_DEBUG(("Setting min height to %.1f\n", enhanced_min_height));
    }

    while (*p) {
	double shift;

	/*
	 * EAM Jun 2009 - treating bytes one at a time does not work for multibyte
	 * encodings, including utf-8. If we hit a byte with the high bit set, test
	 * whether it starts a legal UTF-8 sequence and if so copy the whole thing.
	 * Other multibyte encodings are still a problem.
	 * Gnuplot's other defined encodings are all single-byte; for those we
	 * really do want to treat one byte at a time.
	 */
	if ((*p & 0x80) && (encoding == S_ENC_DEFAULT || encoding == S_ENC_UTF8)) {
	    unsigned long utf8char;
	    const char *nextchar = p;

	    (term->enhanced_open)(fontname, fontsize, base, widthflag, showflag, overprint);
	    if (utf8toulong(&utf8char, &nextchar)) {	/* Legal UTF8 sequence */
		while (p < nextchar)
		    (term->enhanced_writec)(*p++);
		p--;
	    } else {					/* Some other multibyte encoding? */
		(term->enhanced_writec)(*p);
	    }
/* shige : for Shift_JIS */
	} else if ((*p & 0x80) && (encoding == S_ENC_SJIS)) {
	    (term->enhanced_open)(fontname, fontsize, base, widthflag, showflag, overprint);
	    (term->enhanced_writec)(*(p++));
	    (term->enhanced_writec)(*p);
	} else

	switch (*p) {
	case '}'  :
	    /*{{{  deal with it*/
	    if (brace)
		return (p);

	    int_warn(NO_CARET, "enhanced text parser - spurious }");
	    break;
	    /*}}}*/

	case '_'  :
	case '^'  :
	    /*{{{  deal with super/sub script*/
	    shift = (*p == '^') ? 0.5 : -0.3;
	    (term->enhanced_flush)();
	    p = enhanced_recursion(p + 1, FALSE, fontname, fontsize * 0.8,
			      base + shift * fontsize, widthflag,
			      showflag, overprint);
	    break;
	    /*}}}*/
	case '{'  :
	    {
		TBOOLEAN isitalic = FALSE, isbold = FALSE, isnormal = FALSE;
		const char *start_of_fontname = NULL;
		const char *end_of_fontname = NULL;
		char *localfontname = NULL;
		char ch;
		double f = fontsize, ovp;

		/* Mar 2014 - this will hold "fontfamily{:Italic}{:Bold}" */
		char *styledfontname = NULL;

		/*{{{  recurse (possibly with a new font) */

		ENH_DEBUG(("Dealing with {\n"));

		/* 30 Sep 2016:  Remove incorrect whitespace-eating loop going */
		/* waaay back to 31-May-2000 */        /* while (*++p == ' '); */
		++p;
		/* get vertical offset (if present) for overprinted text */
		if (overprint == 2) {
		    char *end;
		    ovp = strtod(p,&end);
		    p = end;
		    if (term->flags & TERM_IS_POSTSCRIPT)
			base = ovp*f;
		    else
			base += ovp*f;
		}
		--p;

		if (*++p == '/') {
		    /* then parse a fontname, optional fontsize */
		    while (*++p == ' ')
			;       /* do nothing */
		    if (*p=='-') {
			while (*++p == ' ')
			    ;   /* do nothing */
		    }
		    start_of_fontname = p;

		    /* Allow font name to be in quotes.
		     * This makes it possible to handle font names containing spaces.
		     */
		    if (*p == '\'' || *p == '"') {
			++p;
			while (*p != '\0' && *p != '}' && *p != *start_of_fontname)
			    ++p;
			if (*p != *start_of_fontname) {
			    int_warn(NO_CARET, "cannot interpret font name %s", start_of_fontname);
			    p = start_of_fontname + 1;
			}
			start_of_fontname++;
			end_of_fontname = p++;
			ch = *p;
		    } else {

		    /* Normal unquoted font name */
			while ((ch = *p) > ' ' && ch != '=' && ch != '*' && ch != '}' && ch != ':')
			    ++p;
			end_of_fontname = p;
		    }

		    do {
			if (ch == '=') {
			    /* get optional font size */
			    char *end;
			    p++;
			    ENH_DEBUG(("Calling strtod(\"%s\") ...", p));
			    f = strtod(p, &end);
			    p = end;
			    ENH_DEBUG(("Returned %.1f and \"%s\"\n", f, p));

			    if (f == 0)
				f = fontsize;
			    else
				f *= enhanced_fontscale;  /* remember the scaling */

			    ENH_DEBUG(("Font size %.1f\n", f));
			} else if (ch == '*') {
			    /* get optional font size scale factor */
			    char *end;
			    p++;
			    ENH_DEBUG(("Calling strtod(\"%s\") ...", p));
			    f = strtod(p, &end);
			    p = end;
			    ENH_DEBUG(("Returned %.1f and \"%s\"\n", f, p));

			    if (f)
				f *= fontsize;  /* apply the scale factor */
			    else
				f = fontsize;

			    ENH_DEBUG(("Font size %.1f\n", f));
			} else if (ch == ':') {
			    /* get optional style markup attributes */
			    p++;
			    if (!strncmp(p,"Bold",4))
				isbold = TRUE;
			    if (!strncmp(p,"Italic",6))
				isitalic = TRUE;
			    if (!strncmp(p,"Normal",6))
				isnormal = TRUE;
			    while (isalpha((unsigned char)*p)) {p++;}
			}
		    } while (((ch = *p) == '=') || (ch == ':') || (ch == '*'));

		    if (ch == '}')
			int_warn(NO_CARET,"bad syntax in enhanced text string");

		    if (*p == ' ')	/* Eat up a single space following a font spec */
			++p;
		    if (!start_of_fontname || (start_of_fontname == end_of_fontname)) {
			/* Use the font name passed in to us */
			localfontname = gp_strdup(fontname);
		    } else {
			/* We found a new font name {/Font ...} */
			int len = end_of_fontname - start_of_fontname;
			localfontname = gp_alloc(len+1,"localfontname");
			strncpy(localfontname, start_of_fontname, len);
			localfontname[len] = '\0';
		    }
		}
		/*}}}*/

		/* Collect cumulative style markup before passing it in the font name */
		isitalic = (wasitalic || isitalic) && !isnormal;
		isbold = (wasbold || isbold) && !isnormal;

		styledfontname = stylefont(localfontname ? localfontname : fontname,
					    isbold, isitalic);

		p = enhanced_recursion(p, TRUE, styledfontname, f, base,
				  widthflag, showflag, overprint);

		(term->enhanced_flush)();

		free(styledfontname);
		free(localfontname);

		break;
	    } /* case '{' */
	case '@' :
	    /*{{{  phantom box - prints next 'char', then restores currentpoint */
	    (term->enhanced_flush)();
	    (term->enhanced_open)(fontname, fontsize, base, widthflag, showflag, 3);
	    p = enhanced_recursion(++p, FALSE, fontname, fontsize, base,
			      widthflag, showflag, overprint);
	    (term->enhanced_open)(fontname, fontsize, base, widthflag, showflag, 4);
	    break;
	    /*}}}*/

	case '&' :
	    /*{{{  character skip - skips space equal to length of character(s) */
	    (term->enhanced_flush)();

	    p = enhanced_recursion(++p, FALSE, fontname, fontsize, base,
			      widthflag, FALSE, overprint);
	    break;
	    /*}}}*/

	case '~' :
	    /*{{{ overprinted text */
	    /* the second string is overwritten on the first, centered
	     * horizontally on the first and (optionally) vertically
	     * shifted by an amount specified (as a fraction of the
	     * current fontsize) at the beginning of the second string

	     * Note that in this implementation neither the under- nor
	     * overprinted string can contain syntax that would result
	     * in additional recursions -- no subscripts,
	     * superscripts, or anything else, with the exception of a
	     * font definition at the beginning of the text */

	    (term->enhanced_flush)();
	    p = enhanced_recursion(++p, FALSE, fontname, fontsize, base,
			      widthflag, showflag, 1);
	    (term->enhanced_flush)();
	    if (!*p)
	        break;
	    p = enhanced_recursion(++p, FALSE, fontname, fontsize, base,
			      FALSE, showflag, 2);

	    overprint = 0;   /* may not be necessary, but just in case . . . */
	    break;
	    /*}}}*/

	case '('  :
	case ')'  :
	    /*{{{  an escape and print it */
	    /* special cases */
	    (term->enhanced_open)(fontname, fontsize, base, widthflag, showflag, overprint);
	    if (term->flags & TERM_IS_POSTSCRIPT)
		(term->enhanced_writec)('\\');
	    (term->enhanced_writec)(*p);
	    break;
	    /*}}}*/

	case '\\'  :
	    /*{{{  various types of escape sequences, some context-dependent */
	    (term->enhanced_open)(fontname, fontsize, base, widthflag, showflag, overprint);

	    /*     Unicode represented as \U+hhhhh where hhhhh is hexadecimal code point.
	     *     For UTF-8 encoding we translate hhhhh to a UTF-8 byte sequence and
	     *     output the bytes one by one.
	     */
	    if (p[1] == 'U' && p[2] == '+') {
		if (encoding == S_ENC_UTF8) {
		    uint32_t codepoint;
		    unsigned char utf8char[8];
		    int i, length;

		    sscanf(&(p[3]), "%5x", &codepoint);
		    length = ucs4toutf8(codepoint, utf8char);
		    p += (codepoint > 0xFFFF) ? 7 : 6;
		    for (i=0; i<length; i++)
			(term->enhanced_writec)(utf8char[i]);
		    break;
		}

	    /*     FIXME: non-utf8 environments not yet supported.
	     *     Note that some terminals may have an alternative way to handle unicode
	     *     escape sequences that is not dependent on encoding.
	     *     E.g. svg and html output could convert to xml sequences &#xhhhh;
	     *     For these cases we must retain the leading backslash so that the
	     *     unicode escape sequence can be recognized by the terminal driver.
	     */
		(term->enhanced_writec)(p[0]);
		break;
	    }

	    /* Enhanced mode always uses \xyz as an octal character representation
	     * but each terminal type must give us the actual output format wanted.
	     * pdf.trm wants the raw character code, which is why we use strtol();
	     * most other terminal types want some variant of "\\%o".
	     */
	    if (p[1] >= '0' && p[1] <= '7') {
		char *e, escape[16], octal[4] = {'\0','\0','\0','\0'};

		octal[0] = *(++p);
		if (p[1] >= '0' && p[1] <= '7') {
		    octal[1] = *(++p);
		    if (p[1] >= '0' && p[1] <= '7')
			octal[2] = *(++p);
		}
		sprintf(escape, enhanced_escape_format, strtol(octal,NULL,8));
		for (e=escape; *e; e++) {
		    (term->enhanced_writec)(*e);
		}
		break;
	    }

	    /* This was the original (prior to version 4) enhanced text code specific
	     * to the reserved characters of PostScript.
	     */
	    if (term->flags & TERM_IS_POSTSCRIPT) {
		if (p[1]=='\\' || p[1]=='(' || p[1]==')') {
		    (term->enhanced_writec)('\\');
		} else if (strchr("^_@&~{}",p[1]) == NULL) {
		    (term->enhanced_writec)('\\');
		    (term->enhanced_writec)('\\');
		    break;
		}
	    }

	    /* Step past the backslash character in the input stream */
	    ++p;

	    /* HBB: Avoid broken output if there's a \ exactly at the end of the line */
	    if (*p == '\0') {
		int_warn(NO_CARET, "enhanced text parser -- spurious backslash");
		break;
	    }

	    /* SVG requires an escaped '&' to be passed as something else */
	    /* FIXME: terminal-dependent code does not belong here */
	    if (*p == '&' && encoding == S_ENC_DEFAULT && !strcmp(term->name, "svg")) {
		(term->enhanced_writec)('\376');
		break;
	    }

	    /* print the character following the backslash */
	    (term->enhanced_writec)(*p);
	    break;
	    /*}}}*/

	default:
	    /*{{{  print it */
	    (term->enhanced_open)(fontname, fontsize, base, widthflag, showflag, overprint);
	    (term->enhanced_writec)(*p);
	    /*}}}*/
	} /* switch (*p) */

	/* like TeX, we only do one character in a recursion, unless it's
	 * in braces
	 */

	if (!brace) {
	    (term->enhanced_flush)();
	    return(p);  /* the ++p in the outer copy will increment us */
	}

	if (*p) /* only not true if { not terminated, I think */
	    ++p;
    } /* while (*p) */

    (term->enhanced_flush)();
    return p;
}


newpath()
{
    if (term->path)
	(*term->path)(0);
}


null_set_color(struct t_colorspec *colorspec)
{
    if (colorspec->type == TC_LT)
	term->linetype(colorspec->lt);
}


term_check_multiplot_okay(TBOOLEAN f_interactive)
{
    FPRINTF((stderr, "term_multiplot_okay(%d)\n", f_interactive));

    if (!term_initialised)
	return;                 /* they've not started yet */

    /* make sure that it is safe to issue an interactive prompt
     * it is safe if
     *   it is not an interactive read, or
     *   the terminal supports interactive multiplot, or
     *   we are not writing to stdout and terminal doesn't
     *     refuse multiplot outright
     */
    if (!f_interactive || (term->flags & TERM_CAN_MULTIPLOT) ||
	((gpoutfile != stdout) && !(term->flags & TERM_CANNOT_MULTIPLOT))
	) {
	/* it's okay to use multiplot here, but suspend first */
	term_suspend();
	return;
    }
    /* uh oh: they're not allowed to be in multiplot here */

    term_end_multiplot();

    /* at this point we know that it is interactive and that the
     * terminal can either only do multiplot when writing to
     * to a file, or it does not do multiplot at all
     */

    if (term->flags & TERM_CANNOT_MULTIPLOT)
	int_error(NO_CARET, "This terminal does not support multiplot");
    else
	int_error(NO_CARET, "Must set output to a file or put all multiplot commands on one input line");
}


check_for_mouse_events()
{
#ifdef USE_MOUSE
    if (term_initialised && term->waitforinput) {
	term->waitforinput(TERM_ONLY_CHECK_MOUSING);
    }
#endif
#ifdef _WIN32
    /* Process windows GUI events (e.g. for text window, or wxt and windows terminals) */
    WinMessageLoop();
    /* On Windows, Ctrl-C only sets this flag. */
    /* The next block duplicates the behaviour of inter(). */
    if (ctrlc_flag) {
	ctrlc_flag = FALSE;
	term_reset();
	putc('\n', stderr);
	fprintf(stderr, "Ctrl-C detected!\n");
	bail_to_command_line();	/* return to prompt */
    }
#endif
}


term_start_plot()
{
    FPRINTF((stderr, "term_start_plot()\n"));

    if (!term_initialised)
	term_initialise();

    if (!term_graphics) {
	FPRINTF((stderr, "- calling term->graphics()\n"));
	(*term->graphics) ();
	term_graphics = TRUE;
    } else if (multiplot && term_suspended) {
	if (term->resume) {
	    FPRINTF((stderr, "- calling term->resume()\n"));
	    (*term->resume) ();
	}
	term_suspended = FALSE;
    }

    if (multiplot)
	multiplot_count++;

    /* Sync point for epslatex text positioning */
    (*term->layer)(TERM_LAYER_RESET);

    /* Because PostScript plots may be viewed out of order, make sure */
    /* Each new plot makes no assumption about the previous palette.  */
    if (term->flags & TERM_IS_POSTSCRIPT)
	invalidate_palette();

    /* Set canvas size to full range of current terminal coordinates */
	canvas.xleft  = 0;
	canvas.xright = term->xmax - 1;
	canvas.ybot   = 0;
	canvas.ytop   = term->ymax - 1;

}


term_reset()
{
    FPRINTF((stderr, "term_reset()\n"));

#ifdef USE_MOUSE
    /* Make sure that ^C will break out of a wait for 'pause mouse' */
    paused_for_mouse = 0;
#ifdef _WIN32
    kill_pending_Pause_dialog();
#endif
#endif

    if (!term_initialised)
	return;

    if (term_suspended) {
	if (term->resume) {
	    FPRINTF((stderr, "- calling term->resume()\n"));
	    (*term->resume) ();
	}
	term_suspended = FALSE;
    }
    if (term_graphics) {
	(*term->text) ();
	term_graphics = FALSE;
    }
    if (term_initialised) {
	(*term->reset) ();
	term_initialised = FALSE;
	/* switch off output to special postscript file (if used) */
	gppsfile = NULL;
    }
}


do_point(unsigned int x, unsigned int y, int number)
{
    int htic, vtic;
    struct termentry *t = term;

    /* use solid lines for point symbols */
    if (term->dashtype != null_dashtype)
	term->dashtype(DASHTYPE_SOLID, NULL);

    if (number < 0) {           /* do dot */
	(*t->move) (x, y);
	(*t->vector) (x, y);
	return;
    }
    number %= POINT_TYPES;
    /* should be in term_tbl[] in later version */
    htic = (term_pointsize * t->h_tic / 2);
    vtic = (term_pointsize * t->v_tic / 2);

    /* point types 1..4 are same as in postscript, png and x11
       point types 5..6 are "similar"
       (note that (number) equals (pointtype-1)
    */
    switch (number) {
    case 4:                     /* do diamond */
	(*t->move) (x - htic, y);
	(*t->vector) (x, y - vtic);
	(*t->vector) (x + htic, y);
	(*t->vector) (x, y + vtic);
	(*t->vector) (x - htic, y);
	(*t->move) (x, y);
	(*t->vector) (x, y);
	break;
    case 0:                     /* do plus */
	(*t->move) (x - htic, y);
	(*t->vector) (x - htic, y);
	(*t->vector) (x + htic, y);
	(*t->move) (x, y - vtic);
	(*t->vector) (x, y - vtic);
	(*t->vector) (x, y + vtic);
	break;
    case 3:                     /* do box */
	(*t->move) (x - htic, y - vtic);
	(*t->vector) (x - htic, y - vtic);
	(*t->vector) (x + htic, y - vtic);
	(*t->vector) (x + htic, y + vtic);
	(*t->vector) (x - htic, y + vtic);
	(*t->vector) (x - htic, y - vtic);
	(*t->move) (x, y);
	(*t->vector) (x, y);
	break;
    case 1:                     /* do X */
	(*t->move) (x - htic, y - vtic);
	(*t->vector) (x - htic, y - vtic);
	(*t->vector) (x + htic, y + vtic);
	(*t->move) (x - htic, y + vtic);
	(*t->vector) (x - htic, y + vtic);
	(*t->vector) (x + htic, y - vtic);
	break;
    case 5:                     /* do triangle */
	(*t->move) (x, y + (4 * vtic / 3));
	(*t->vector) (x - (4 * htic / 3), y - (2 * vtic / 3));
	(*t->vector) (x + (4 * htic / 3), y - (2 * vtic / 3));
	(*t->vector) (x, y + (4 * vtic / 3));
	(*t->move) (x, y);
	(*t->vector) (x, y);
	break;
    case 2:                     /* do star */
	(*t->move) (x - htic, y);
	(*t->vector) (x - htic, y);
	(*t->vector) (x + htic, y);
	(*t->move) (x, y - vtic);
	(*t->vector) (x, y - vtic);
	(*t->vector) (x, y + vtic);
	(*t->move) (x - htic, y - vtic);
	(*t->vector) (x - htic, y - vtic);
	(*t->vector) (x + htic, y + vtic);
	(*t->move) (x - htic, y + vtic);
	(*t->vector) (x - htic, y + vtic);
	(*t->vector) (x + htic, y - vtic);
	break;
    }
}


MOVE_null(unsigned int x, unsigned int y)
{
    (void) x;                   /* avoid -Wunused warning */
    (void) y;
}


load_linetype(struct lp_style_type *lp, int tag)
{
    struct linestyle_def *this;
    TBOOLEAN recycled = FALSE;

recycle:

    if ((tag > 0)
    && (monochrome || (term && (term->flags & TERM_MONOCHROME)))) {
	for (this = first_mono_linestyle; this; this = this->next) {
	    if (tag == this->tag) {
		*lp = this->lp_properties;
		return;
	    }
	}

	/* This linetype wasn't defined explicitly.		*/
	/* Should we recycle one of the first N linetypes?	*/
	if (tag > mono_recycle_count && mono_recycle_count > 0) {
	    tag = (tag-1) % mono_recycle_count + 1;
	    goto recycle;
	}

	return;
    }

    this = first_perm_linestyle;
    while (this != NULL) {
	if (this->tag == tag) {
	    /* Always load color, width, and dash properties */
	    lp->l_type = this->lp_properties.l_type;
	    lp->l_width = this->lp_properties.l_width;
	    lp->pm3d_color = this->lp_properties.pm3d_color;
	    lp->d_type = this->lp_properties.d_type;
	    lp->custom_dash_pattern = this->lp_properties.custom_dash_pattern;

	    /* Needed in version 5.0 to handle old terminals (pbm hpgl ...) */
	    /* with no support for user-specified colors */
	    if (term && term->set_color == null_set_color)
		lp->l_type = tag;

	    /* Do not recycle point properties. */
	    /* FIXME: there should be a separate command "set pointtype cycle N" */
	    if (!recycled) {
	    	lp->p_type = this->lp_properties.p_type;
	    	lp->p_interval = this->lp_properties.p_interval;
	    	lp->p_size = this->lp_properties.p_size;
	    	memcpy(lp->p_char, this->lp_properties.p_char, sizeof(lp->p_char));
	    }
	    return;
	} else {
	    this = this->next;
	}
    }

    /* This linetype wasn't defined explicitly.		*/
    /* Should we recycle one of the first N linetypes?	*/
    if (tag > linetype_recycle_count && linetype_recycle_count > 0) {
	tag = (tag-1) % linetype_recycle_count + 1;
	recycled = TRUE;
	goto recycle;
    }

    /* No user-defined linetype with this tag; fall back to default line type. */
    /* NB: We assume that the remaining fields of lp have been initialized. */
    lp->l_type = tag - 1;
    lp->pm3d_color.type = TC_LT;
    lp->pm3d_color.lt = lp->l_type;
    lp->d_type = DASHTYPE_SOLID;
    lp->p_type = (tag <= 0) ? -1 : tag - 1;
}


term_end_plot()
{
    FPRINTF((stderr, "term_end_plot()\n"));

    if (!term_initialised)
	return;

    /* Sync point for epslatex text positioning */
    (*term->layer)(TERM_LAYER_END_TEXT);

    if (!multiplot) {
	FPRINTF((stderr, "- calling term->text()\n"));
	(*term->text) ();
	term_graphics = FALSE;
    } else {
	multiplot_next();
    }

#ifdef VMS
    if (opened_binary)
	fflush_binary();
    else
#endif /* VMS */
	(void) fflush(gpoutfile);

#ifdef USE_MOUSE
    if (term->set_ruler) {
	recalc_statusline();
	update_ruler();
    }
#endif
}


LINETYPE_null(int t)
{
    (void) t;                   /* avoid -Wunused warning */
}


PUTTEXT_null(unsigned int x, unsigned int y, const char *s)
{
    (void) s;                   /* avoid -Wunused warning */
    (void) x;
    (void) y;
}


null_set_font(const char *font)
{
    (void) font;		/* avoid -Wunused warning */
    return FALSE;		/* Never used!! */
}


init_monochrome()
{
    struct lp_style_type mono_default[] = DEFAULT_MONO_LINETYPES;

    if (first_mono_linestyle == NULL) {
	int i, n = sizeof(mono_default) / sizeof(struct lp_style_type);
	struct linestyle_def *new;
	/* copy default list into active list */
	for (i=n; i>0; i--) {
	    new = gp_alloc(sizeof(struct linestyle_def), NULL);
	    new->next = first_mono_linestyle;
	    new->lp_properties = mono_default[i-1];
	    new->tag = i;
	    first_mono_linestyle = new;
	}
    }
}


set_term()
{
    struct termentry *t = NULL;
    char *input_name = NULL;

    if (!END_OF_COMMAND) {
	input_name = gp_input_line + token[c_token].start_index;
	t = change_term(input_name, token[c_token].length);
	if (!t && isstringvalue(c_token) && (input_name = try_to_get_string())) {
	    if (strchr(input_name, ' '))
		*strchr(input_name, ' ') = '\0';
	    t = change_term(input_name, strlen(input_name));
	    free(input_name);
	} else {
	    c_token++;
	}
    }

    if (!t) {
	change_term("unknown", 7);
	int_error(c_token-1, "unknown or ambiguous terminal type; type just 'set terminal' for a list");
    }

    /* otherwise the type was changed */
    return (t);
}


estimate_plaintext(char *enhancedtext)
{
    if (enhancedtext == NULL)
	return NULL;
    estimate_strlen(enhancedtext, NULL);
    return ENHest_plaintext;
}


null_scale(double x, double y)
{
    (void) x;                   /* avoid -Wunused warning */
    (void) y;
    int_error(NO_CARET, "Attempt to call deprecated terminal function");
    return FALSE;               /* can't be done */
}


do_enh_writec(int c)
{
    /* note: c is meant to hold a char, but is actually an int, for
     * the same reasons applying to putc() and friends */
    *enhanced_cur_text++ = c;
}


null_text_angle(int ang)
{
    return (ang == 0);
}


load_dashtype(struct t_dashtype *dt, int tag)
{
    struct custom_dashtype_def *this;
    struct t_dashtype loc_dt = DEFAULT_DASHPATTERN;

    this = first_custom_dashtype;
    while (this != NULL) {
	if (this->tag == tag) {
	    *dt = this->dashtype;
	    memcpy(dt->dstring, this->dashtype.dstring, sizeof(dt->dstring));
	    return this->d_type;
	} else {
	    this = this->next;
	}
    }

    /* not found, fall back to default, terminal-dependent dashtype */
    *dt = loc_dt;
    return tag - 1;
}


term_initialise()
{
    FPRINTF((stderr, "term_initialise()\n"));

    if (!term)
	int_error(NO_CARET, "No terminal defined");

    /* check if we have opened the output file in the wrong mode
     * (text/binary), if set term comes after set output
     * This was originally done in change_term, but that
     * resulted in output files being truncated
     */

    if (outstr && (term->flags & TERM_NO_OUTPUTFILE)) {
	if (interactive)
	    fprintf(stderr,"Closing %s\n",outstr);
	term_close_output();
    }

    if (outstr &&
	(((term->flags & TERM_BINARY) && !opened_binary) ||
	 ((!(term->flags & TERM_BINARY) && opened_binary)))) {
	/* this is nasty - we cannot just term_set_output(outstr)
	 * since term_set_output will first free outstr and we
	 * end up with an invalid pointer. I think I would
	 * prefer to defer opening output file until first plot.
	 */
	char *temp = gp_alloc(strlen(outstr) + 1, "temp file string");
	if (temp) {
	    FPRINTF((stderr, "term_initialise: reopening \"%s\" as %s\n",
		     outstr, term->flags & TERM_BINARY ? "binary" : "text"));
	    strcpy(temp, outstr);
	    term_set_output(temp);      /* will free outstr */
	    if (temp != outstr) {
		if (temp)
		    free(temp);
		temp = outstr;
	    }
	} else
	    fputs("Cannot reopen output file in binary", stderr);
	/* and carry on, hoping for the best ! */
    }
#if defined(MSDOS) || defined (_WIN32) || defined(OS2)
# ifdef _WIN32
    else if (!outstr && (term->flags & TERM_BINARY))
# else
    else if (!outstr && !interactive && (term->flags & TERM_BINARY))
# endif
	{
#if defined(_WIN32) && !defined(WGP_CONSOLE)
#ifdef PIPES
	    if (!output_pipe_open)
#endif
		if (outstr == NULL && !(term->flags & TERM_NO_OUTPUTFILE))
		    int_error(c_token, "cannot output binary data to wgnuplot text window");
#endif
	    /* binary to stdout in non-interactive session... */
	    fflush(stdout);
	    setmode(fileno(stdout), O_BINARY);
	}
#endif

    if (!term_initialised || term_force_init) {
	FPRINTF((stderr, "- calling term->init()\n"));
	(*term->init) ();
	term_initialised = TRUE;
#ifdef HAVE_LOCALE_H
	/* This is here only from an abundance of caution (a.k.a. paranoia).
	 * Some terminals (wxt qt caca) are known to change the locale when
	 * initialized.  Others have been implicated (gd).  Rather than trying
	 * to catch all such offenders one by one, cover for all of them here.
	 */
	setlocale(LC_NUMERIC, "C");
#endif
    }
}


do_pointsize(double size)
{
    term_pointsize = (size >= 0 ? size : 1);
}


get_terminals_names()
{
    int i;
    char *buf = gp_alloc(TERMCOUNT*15, "all_term_names"); /* max 15 chars per name */
    char *names;
    int sort_idxs[TERMCOUNT];

    /* sort terminal types alphabetically */
    for( i = 0; i < TERMCOUNT; i++ )
	sort_idxs[i] = i;
    qsort( sort_idxs, TERMCOUNT, sizeof(int), termcomp );
    /* now sort_idxs[] contains the sorted indices */

    strcpy(buf, " "); /* let the string have leading and trailing " " in order to search via strstrt(GPVAL_TERMINALS, " png "); */
    for (i = 0; i < TERMCOUNT; i++)
	sprintf(buf+strlen(buf), "%s ", term_tbl[sort_idxs[i]].name);
    names = gp_alloc(strlen(buf)+1, "all_term_names2");
    strcpy(names, buf);
    free(buf);
    return names;
}


change_term(const char *origname, int length)
{
    int i;
    struct termentry *t = NULL;
    TBOOLEAN ambiguous = FALSE;

    /* For backwards compatibility only */
    char *name = (char *)origname;
    if (!strncmp(origname,"X11",length)) {
	name = "x11";
	length = 3;
    }

#ifdef HAVE_CAIROPDF
    /* To allow "set term eps" as short for "set term epscairo" */
    if (!strncmp(origname,"eps",length)) {
	name = "epscairo";
	length = 8;
    }
#endif
#ifdef HAVE_LIBGD
    /* To allow "set term sixel" as short for "set term sixelgd" */
    if (!strncmp(origname, "sixel", length)) {
	name = "sixelgd";
	length = 7;
    }
#endif

    for (i = 0; i < TERMCOUNT; i++) {
	if (!strncmp(name, term_tbl[i].name, length)) {
	    if (t)
		ambiguous = TRUE;
	    t = term_tbl + i;
	    /* Exact match is always accepted */
	    if (length == strlen(term_tbl[i].name)) {
		ambiguous = FALSE;
		break;
	    }
	}
    }

    if (!t || ambiguous)
	return (NULL);

    /* Success: set terminal type now */

    term = t;
    term_initialised = FALSE;

    /* check that optional fields are initialised to something */
    if (term->text_angle == 0)
	term->text_angle = null_text_angle;
    if (term->justify_text == 0)
	term->justify_text = null_justify_text;
    if (term->point == 0)
	term->point = do_point;
    if (term->arrow == 0)
	term->arrow = do_arrow;
    if (term->pointsize == 0)
	term->pointsize = do_pointsize;
    if (term->linewidth == 0)
	term->linewidth = null_linewidth;
    if (term->layer == 0)
	term->layer = null_layer;
    if (term->tscale <= 0)
	term->tscale = 1.0;
    if (term->set_font == 0)
	term->set_font = null_set_font;
    if (term->set_color == 0) {
	term->set_color = null_set_color;
	term->flags |= TERM_NULL_SET_COLOR;
    }
    if (term->dashtype == 0)
	term->dashtype = null_dashtype;

    if (interactive)
	fprintf(stderr, "\nTerminal type is now '%s'\n", term->name);

    /* Invalidate any terminal-specific structures that may be active */
    invalidate_palette();

    return (t);
}


term_close_output()
{
    FPRINTF((stderr, "term_close_output\n"));

    opened_binary = FALSE;

    if (!outstr)                /* ie using stdout */
	return;

#if defined(PIPES)
    if (output_pipe_open) {
	(void) pclose(gpoutfile);
	output_pipe_open = FALSE;
    } else
#endif /* PIPES */
#ifdef _WIN32
    if (stricmp(outstr, "PRN") == 0)
	close_printer(gpoutfile);
    else
#endif
    if (gpoutfile != gppsfile)
	fclose(gpoutfile);

    gpoutfile = stdout;         /* Don't dup... */
    free(outstr);
    outstr = NULL;

    if (gppsfile)
	fclose(gppsfile);
    gppsfile = NULL;
}


options_null()
{
    term_options[0] = '\0';     /* we have no options */
}


do_arc(
    int cx, int cy, /* Center */
    double radius, /* Radius */
    double arc_start, double arc_end, /* Limits of arc in degrees */
    int style, TBOOLEAN wedge)
{
    gpiPoint vertex[250];
    int i, segments;
    double aspect;
    TBOOLEAN complete_circle;

    /* Protect against out-of-range values */
    while (arc_start < 0)
	arc_start += 360.;
    while (arc_end > 360.)
	arc_end -= 360.;

    /* Always draw counterclockwise */
    while (arc_end < arc_start)
	arc_end += 360.;

    /* Choose how finely to divide this arc into segments */
    /* Note: INC=2 caused problems for gnuplot_x11 */
#   define INC 3.
    segments = (arc_end - arc_start) / INC;
    if (segments < 1)
	segments = 1;

    /* Calculate the vertices */
    aspect = (double)term->v_tic / (double)term->h_tic;
    for (i=0; i<segments; i++) {
	vertex[i].x = cx + cos(DEG2RAD * (arc_start + i*INC)) * radius;
	vertex[i].y = cy + sin(DEG2RAD * (arc_start + i*INC)) * radius * aspect;
    }
#   undef INC
    vertex[segments].x = cx + cos(DEG2RAD * arc_end) * radius;
    vertex[segments].y = cy + sin(DEG2RAD * arc_end) * radius * aspect;

    if (fabs(arc_end - arc_start) > .1
    &&  fabs(arc_end - arc_start) < 359.9) {
	vertex[++segments].x = cx;
	vertex[segments].y = cy;
	vertex[++segments].x = vertex[0].x;
	vertex[segments].y = vertex[0].y;
	complete_circle = FALSE;
    } else
	complete_circle = TRUE;

    if (style) { /* Fill in the center */
	gpiPoint fillarea[250];
	int in;

	clip_polygon(vertex, fillarea, segments, &in);
	fillarea[0].style = style;
	if (term->filled_polygon)
	    term->filled_polygon(in, fillarea);

    } else { /* Draw the arc */
	if (!wedge && !complete_circle)
	    segments -= 2;
	draw_clip_polygon(segments+1, vertex);
    }
}


enh_err_check(const char *str)
{
    if (*str == '}')
	int_warn(NO_CARET, "enhanced text mode parser - ignoring spurious }");
    else
	int_warn(NO_CARET, "enhanced text mode parsing error");
}