#ifndef lint
static char *RCSid() { return RCSid("$Id: gadgets.c,v 1.115.2.1 2014/09/05 21:50:56 sfeam Exp $"); }
#endif

/* GNUPLOT - gadgets.c */

/*[
 * Copyright 2000, 2004   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
]*/

#include "gadgets.h"
#include "alloc.h"
#include "command.h"
#include "graph3d.h" /* for map3d_position_r() */
#include "graphics.h"
#include "plot3d.h" /* For is_plot_with_palette() */

#include "pm3d.h"

/* This file contains mainly a collection of global variables that
 * used to be in 'set.c', where they didn't really belong. They
 * describe the status of several parts of the gnuplot plotting engine
 * that are used by both 2D and 3D plots, and thus belong neither to
 * graphics.c nor graph3d.c, alone. This is not a very clean solution,
 * but better than mixing internal status and the user interface as we
 * used to have it, in set.c and setshow.h */

legend_key keyT = DEFAULT_KEY_PROPS;

/* Description of the color box associated with CB_AXIS */
color_box_struct color_box; /* initialized in init_color() */
color_box_struct default_color_box = {SMCOLOR_BOX_DEFAULT, 'v', 1, LT_BLACK, LAYER_FRONT, 0,
					{screen, screen, screen, 0.90, 0.2, 0.0},
					{screen, screen, screen, 0.05, 0.6, 0.0},
					{0,0,0,0} };

/* The graph box, in terminal coordinates, as calculated by boundary()
 * or boundary3d(): */
BoundingBox plot_bounds;

/* The bounding box for the entire drawable area  of current terminal */
BoundingBox canvas;

/* The bounding box against which clipping is to be done */
BoundingBox *clip_area = &plot_bounds;

/* 'set size', 'set origin' setttings */
float xsize = 1.0;		/* scale factor for size */
float ysize = 1.0;		/* scale factor for size */
float zsize = 1.0;		/* scale factor for size */
float xoffset = 0.0;		/* x origin */
float yoffset = 0.0;		/* y origin */
float aspect_ratio = 0.0;	/* don't attempt to force it */
int aspect_ratio_3D = 0;	/* 2 will put x and y on same scale, 3 for z also */

/* EAM Augest 2006 - 
   redefine margin as t_position so that absolute placement is possible */
/* space between left edge and plot_bounds.xleft in chars (-1: computed) */
t_position lmargin = DEFAULT_MARGIN_POSITION;
/* space between bottom and plot_bounds.ybot in chars (-1: computed) */
t_position bmargin = DEFAULT_MARGIN_POSITION;
/* space between right egde and plot_bounds.xright in chars (-1: computed) */
t_position rmargin = DEFAULT_MARGIN_POSITION;
/* space between top egde and plot_bounds.ytop in chars (-1: computed) */
t_position tmargin = DEFAULT_MARGIN_POSITION;

/* Pointer to first 'set dashtype' definition in linked list */
struct custom_dashtype_def *first_custom_dashtype = NULL;

/* Pointer to the start of the linked list of 'set label' definitions */
struct text_label *first_label = NULL;

/* Pointer to first 'set linestyle' definition in linked list */
struct linestyle_def *first_linestyle = NULL;
struct linestyle_def *first_perm_linestyle = NULL;

/* Pointer to first 'set style arrow' definition in linked list */
struct arrowstyle_def *first_arrowstyle = NULL;

/* Holds the properties from 'set style parallelaxis' */
struct pa_style parallel_axis_style = DEFAULT_PARALLEL_AXIS_STYLE;

/* set arrow */
struct arrow_def *first_arrow = NULL;

#ifdef EAM_OBJECTS
/* Pointer to first object instance in linked list */
struct object *first_object = NULL;
#endif

/* 'set title' status */
text_label title = EMPTY_LABELSTRUCT;

/* 'set timelabel' status */
text_label timelabel = EMPTY_LABELSTRUCT;
int timelabel_rotate = FALSE;
int timelabel_bottom = TRUE;

/* flag for polar mode */
TBOOLEAN polar = FALSE;

/* zero threshold, may _not_ be 0! */
double zero = ZERO;

/* Status of 'set pointsize' and 'set pointintervalbox' commands */
double pointsize = 1.0;
double pointintervalbox = 1.0;

/* set border */
int draw_border = 31;	/* The current settings */
int user_border = 31;	/* What the user last set explicitly */
int border_layer = LAYER_FRONT;
# define DEFAULT_BORDER_LP { 0, LT_BLACK, 0, DASHTYPE_SOLID, 0, 1.0, 1.0, 0, BLACK_COLORSPEC, DEFAULT_DASHPATTERN }
struct lp_style_type border_lp = DEFAULT_BORDER_LP;
const struct lp_style_type default_border_lp = DEFAULT_BORDER_LP;
const struct lp_style_type background_lp = {0, LT_BACKGROUND, 0, DASHTYPE_SOLID, 0, 1.0, 0.0, 0, BACKGROUND_COLORSPEC, DEFAULT_DASHPATTERN};

/* set clip */
TBOOLEAN clip_lines1 = TRUE;
TBOOLEAN clip_lines2 = FALSE;
TBOOLEAN clip_points = FALSE;

static int clip_line __PROTO((int *, int *, int *, int *));

/* set samples */
int samples_1 = SAMPLES;
int samples_2 = SAMPLES;

/* set angles */
double ang2rad = 1.0;		/* 1 or pi/180, tracking angles_format */

enum PLOT_STYLE data_style = POINTSTYLE;
enum PLOT_STYLE func_style = LINES;

TBOOLEAN parametric = FALSE;

/* If last plot was a 3d one. */
TBOOLEAN is_3d_plot = FALSE;

/* Flag to signal that the existing data is valid for a quick refresh */
TRefresh_Allowed refresh_ok = E_REFRESH_NOT_OK;
/* FIXME: do_plot should be able to figure this out on its own! */
int refresh_nplots = 0;
/* Flag to show that volatile input data is present */
TBOOLEAN volatile_data = FALSE;

fill_style_type default_fillstyle = { FS_EMPTY, 100, 0, DEFAULT_COLORSPEC } ;

#ifdef EAM_OBJECTS
/* Default rectangle style - background fill, black border */
struct object default_rectangle = DEFAULT_RECTANGLE_STYLE;
struct object default_circle = DEFAULT_CIRCLE_STYLE;
struct object default_ellipse = DEFAULT_ELLIPSE_STYLE;
#endif

/* filledcurves style options */
filledcurves_opts filledcurves_opts_data = EMPTY_FILLEDCURVES_OPTS;
filledcurves_opts filledcurves_opts_func = EMPTY_FILLEDCURVES_OPTS;

#if TRUE || defined(BACKWARDS_COMPATIBLE)
/* Prefer line styles over plain line types */
TBOOLEAN prefer_line_styles = FALSE;
#endif

/* If current terminal claims to be monochrome, don't try to send it colors */
#define monochrome_terminal ((t->flags & TERM_MONOCHROME) != 0)

histogram_style histogram_opts = DEFAULT_HISTOGRAM_STYLE;

boxplot_style boxplot_opts = DEFAULT_BOXPLOT_STYLE;

/* WINDOWID to be filled by terminals running on X11 (x11, wxt, qt, ...) */
int current_x11_windowid = 0;

#ifdef EAM_BOXED_TEXT
textbox_style textbox_opts = DEFAULT_TEXTBOX_STYLE;
#endif

/*****************************************************************/
/* Routines that deal with global objects defined in this module */
/*****************************************************************/

/* Clipping to the bounding box: */

/* Test a single point to be within the BoundingBox.
 * Sets the returned integers 4 l.s.b. as follows:
 * bit 0 if to the left of xleft.
 * bit 1 if to the right of xright.
 * bit 2 if below of ybot.
 * bit 3 if above of ytop.
 * 0 is returned if inside.
 */
int
clip_point(unsigned int x, unsigned int y)
{
    int ret_val = 0;

    if (!clip_area)
	return 0;
    if ((int)x < clip_area->xleft)
	ret_val |= 0x01;
    if ((int)x > clip_area->xright)
	ret_val |= 0x02;
    if ((int)y < clip_area->ybot)
	ret_val |= 0x04;
    if ((int)y > clip_area->ytop)
	ret_val |= 0x08;

    return ret_val;
}

/* Clip the given line to drawing coords defined by BoundingBox.
 *   This routine uses the cohen & sutherland bit mapping for fast clipping -
 * see "Principles of Interactive Computer Graphics" Newman & Sproull page 65.
 */
void
draw_clip_line(int x1, int y1, int x2, int y2)
{
    struct termentry *t = term;

    if (!clip_line(&x1, &y1, &x2, &y2))
	/* clip_line() returns zero --> segment completely outside
	 * bounding box */
	return;

    (*t->move) (x1, y1);
    (*t->vector) (x2, y2);
}

/* Draw a contiguous line path which may be clipped. Compared to
 * draw_clip_line(), this routine moves to a coordinate only when
 * necessary.
 */
void 
draw_clip_polygon(int points, gpiPoint *p) 
{
    int i;
    int x1, y1, x2, y2;
    int pos1, pos2, clip_ret;
    struct termentry *t = term;

    if (points <= 1) 
	return;

    x1 = p[0].x;
    y1 = p[0].y;
    pos1 = clip_point(x1, y1);
    if (!pos1) /* move to first point if it is inside */
	(*t->move)(x1, y1);

    for (i = 1; i < points; i++) {
	x2 = p[i].x;
	y2 = p[i].y;
	pos2 = clip_point(x2, y2);
	clip_ret = clip_line(&x1, &y1, &x2, &y2);

	if (clip_ret) {
	    /* there is a line to draw */
	    if (pos1) /* first vertex was recalculated, move to new start point */
		(*t->move)(x1, y1);
	    (*t->vector)(x2, y2);
	}

	x1 = p[i].x;
	y1 = p[i].y;
	/* The end point and the line do not necessarily have the same
	 * status. The end point can be 'inside', but the whole line is
	 * 'outside'. Do not update pos1 in this case.  Bug #1268.
	 * FIXME: This is papering over an inconsistency in coordinate
	 * calculation somewhere else!
	 */
	if (!(clip_ret == 0 && pos2 == 0))
	    pos1 = pos2;
    }
}

void
draw_clip_arrow( int sx, int sy, int ex, int ey, int head)
{
    struct termentry *t = term;

    /* Don't draw head if the arrow itself is clipped */
    if (clip_point(sx,sy))
	head &= ~BACKHEAD;
    if (clip_point(ex,ey))
	head &= ~END_HEAD;
    clip_line(&sx, &sy, &ex, &ey);

    /* Call terminal routine to draw the clipped arrow */
    (*t->arrow)((unsigned int)sx, (unsigned int)sy,
		(unsigned int)ex, (unsigned int)ey, head);
}

/* Clip the given line to drawing coords defined by BoundingBox.
 *   This routine uses the cohen & sutherland bit mapping for fast clipping -
 * see "Principles of Interactive Computer Graphics" Newman & Sproull page 65.
 * Return 0: entire line segment is outside bounding box
 *        1: entire line segment is inside bounding box
 *       -1: line segment has been clipped to bounding box
 */
int
clip_line(int *x1, int *y1, int *x2, int *y2)
{
    /* Apr 2014: This algorithm apparently assumed infinite precision
     * integer arithmetic. It was failing when passed coordinates that
     * were hugely out of bounds because tests for signedness of the
     * form (dx * dy > 0) would overflow rather than correctly evaluating
     * to (sign(dx) == sign(dy)).  Worse yet, the numerical values are
     * used to determine which end of the segment to update.
     * This is now addressed by making dx and dy (double) rather than (int)
     * but it might be better to hard-code the sign tests.
     */
    double dx, dy;

    int x, y, x_intr[4], y_intr[4], count, pos1, pos2;
    int x_max, x_min, y_max, y_min;
    pos1 = clip_point(*x1, *y1);
    pos2 = clip_point(*x2, *y2);
    if (!pos1 && !pos2)
	return 1;		/* segment is totally in */
    if (pos1 & pos2)
	return 0;		/* segment is totally out. */
    /* Here part of the segment MAY be inside. test the intersection
     * of this segment with the 4 boundaries for hopefully 2 intersections
     * in. If none are found segment is totaly out.
     * Under rare circumstances there may be up to 4 intersections (e.g.
     * when the line passes directly through at least one corner).
     */
    count = 0;
    dx = *x2 - *x1;
    dy = *y2 - *y1;
    /* Find intersections with the x parallel bbox lines: */
    if (dy != 0) {
	x = (clip_area->ybot - *y2) * dx / dy + *x2;	/* Test for clip_area->ybot boundary. */
	if (x >= clip_area->xleft && x <= clip_area->xright) {
	    x_intr[count] = x;
	    y_intr[count++] = clip_area->ybot;
	}
	x = (clip_area->ytop - *y2) * dx / dy + *x2;	/* Test for clip_area->ytop boundary. */
	if (x >= clip_area->xleft && x <= clip_area->xright) {
	    x_intr[count] = x;
	    y_intr[count++] = clip_area->ytop;
	}
    }
    /* Find intersections with the y parallel bbox lines: */
    if (dx != 0) {
	y = (clip_area->xleft - *x2) * dy / dx + *y2;	/* Test for clip_area->xleft boundary. */
	if (y >= clip_area->ybot && y <= clip_area->ytop) {
	    x_intr[count] = clip_area->xleft;
	    y_intr[count++] = y;
	}
	y = (clip_area->xright - *x2) * dy / dx + *y2;	/* Test for clip_area->xright boundary. */
	if (y >= clip_area->ybot && y <= clip_area->ytop) {
	    x_intr[count] = clip_area->xright;
	    y_intr[count++] = y;
	}
    }
    if (count < 2)
	return 0;

    /* check which intersections to use, for more than two intersections the first two may be identical */
    if ((count > 2) && (x_intr[0] == x_intr[1]) && (y_intr[0] == y_intr[1])) {
	x_intr[1] = x_intr[2];
	y_intr[1] = y_intr[2];
    }	

    if (*x1 < *x2) {
	x_min = *x1;
	x_max = *x2;
    } else {
	x_min = *x2;
	x_max = *x1;
    }
    if (*y1 < *y2) {
	y_min = *y1;
	y_max = *y2;
    } else {
	y_min = *y2;
	y_max = *y1;
    }

    if (pos1 && pos2) {		/* Both were out - update both */
	/* EAM Sep 2008 - preserve direction of line segment */
	if ((dx*(x_intr[1]-x_intr[0]) < 0)
	||  (dy*(y_intr[1]-y_intr[0]) < 0)) {
	    *x1 = x_intr[1];
	    *y1 = y_intr[1];
	    *x2 = x_intr[0];
	    *y2 = y_intr[0];
	} else {
	    *x1 = x_intr[0];
	    *y1 = y_intr[0];
	    *x2 = x_intr[1];
	    *y2 = y_intr[1];
	}
    } else if (pos1) {		/* Only x1/y1 was out - update only it */
	/* Nov 2010: When clip_line() and draw_clip_line() were consolidated in */
	/* 2000, the test below was the only point of difference between them.  */
	/* Unfortunately, the wrong version was kept. Now I change it back.     */
	/* The effect of the wrong version (>= rather than >) was that a line   */
	/* from ymin to ymax+eps was clipped to ymin,ymin rather than ymin,ymax */
	if (dx * (*x2 - x_intr[0]) + dy * (*y2 - y_intr[0]) > 0) {
	    *x1 = x_intr[0];
	    *y1 = y_intr[0];
	} else {
	    *x1 = x_intr[1];
	    *y1 = y_intr[1];
	}
    } else {			/* Only x2/y2 was out - update only it */
	/* Same difference here, again */
	if (dx * (x_intr[0] - *x1) + dy * (y_intr[0] - *y1) > 0) {
	    *x2 = x_intr[0];
	    *y2 = y_intr[0];
	} else {
	    *x2 = x_intr[1];
	    *y2 = y_intr[1];
	}
    }

    if (*x1 < x_min || *x1 > x_max || *x2 < x_min || *x2 > x_max || *y1 < y_min || *y1 > y_max || *y2 < y_min || *y2 > y_max)
	return 0;

    return -1;
}

/* test if coordinates of a vertex are inside boundary box. The start
   and end points for the clip_boundary must be in correct order for
   this to work properly (see respective definitions in clip_polygon()). */
TBOOLEAN
vertex_is_inside(gpiPoint test_vertex, gpiPoint *clip_boundary)
{
    if (clip_boundary[1].x > clip_boundary[0].x)              /*bottom edge*/
	if (test_vertex.y >= clip_boundary[0].y) return TRUE;
    if (clip_boundary[1].x < clip_boundary[0].x)              /*top edge*/
	if (test_vertex.y <= clip_boundary[0].y) return TRUE;
    if (clip_boundary[1].y > clip_boundary[0].y)              /*right edge*/
	if (test_vertex.x <= clip_boundary[1].x) return TRUE;
    if (clip_boundary[1].y < clip_boundary[0].y)              /*left edge*/
	if (test_vertex.x >= clip_boundary[1].x) return TRUE;
    return FALSE;
} 

void
intersect_polyedge_with_boundary(gpiPoint first, gpiPoint second, gpiPoint *intersect, gpiPoint *clip_boundary)
{
    /* this routine is called only if one point is outside and the other
       is inside, which implies that clipping is needed at a horizontal
       boundary, that second.y is different from first.y and no division
       by zero occurs. Same for vertical boundary and x coordinates. */
    if (clip_boundary[0].y == clip_boundary[1].y) { /* horizontal */
	(*intersect).y = clip_boundary[0].y;
	(*intersect).x = first.x + (clip_boundary[0].y - first.y) * (second.x - first.x)/(second.y - first.y);
    } else { /* vertical */
	(*intersect).x = clip_boundary[0].x;
	(*intersect).y = first.y + (clip_boundary[0].x - first.x) * (second.y - first.y)/(second.x - first.x);
    }
}

/* Clip the given polygon to a single edge of the bounding box. */
void 
clip_polygon_to_boundary(gpiPoint *in, gpiPoint *out, int in_length, int *out_length, gpiPoint *clip_boundary)
{
    gpiPoint prev, curr; /* start and end point of current polygon edge. */
    int j;

    *out_length = 0;
    if (in_length <= 0)
	return;
    else
	prev = in[in_length - 1]; /* start with the last vertex */

    for (j = 0; j < in_length; j++) {
	curr = in[j];
	if (vertex_is_inside(curr, clip_boundary)) {
	    if (vertex_is_inside(prev, clip_boundary)) {
		/* both are inside, add current vertex */
		out[*out_length] = in[j];
		(*out_length)++;
	    } else {
		/* changed from outside to inside, add intersection point and current point */
		intersect_polyedge_with_boundary(prev, curr, out+(*out_length), clip_boundary);
		out[*out_length+1] = curr;
		*out_length += 2;
	    }
	} else {
	    if (vertex_is_inside(prev, clip_boundary)) {
		/* changed from inside to outside, add intersection point */
		intersect_polyedge_with_boundary(prev, curr, out+(*out_length), clip_boundary);
		(*out_length)++;
	    }
	}
	prev = curr;
    }
}

/* Clip the given polygon to drawing coords defined by BoundingBox.
 * This routine uses the Sutherland-Hodgman algorithm.  When calling
 * this function, you must make sure that you reserved enough
 * memory for the output polygon. out_length can be as big as
 * 2*(in_length - 1)
 */
void
clip_polygon(gpiPoint *in, gpiPoint *out, int in_length, int *out_length)
{
    int i;
    gpiPoint clip_boundary[5];
    static gpiPoint *tmp_corners = NULL;

    if (!clip_area) {
	memcpy(out, in, in_length * sizeof(gpiPoint));
	*out_length = in_length;
	return;
    }
    tmp_corners = gp_realloc(tmp_corners, 2 * in_length * sizeof(gpiPoint), "clip_polygon");

    /* vertices of the rectangular clipping window starting from
       top-left in counterclockwise direction */
    clip_boundary[0].x = clip_area->xleft;  /* top left */
    clip_boundary[0].y = clip_area->ytop;
    clip_boundary[1].x = clip_area->xleft;  /* bottom left */
    clip_boundary[1].y = clip_area->ybot;
    clip_boundary[2].x = clip_area->xright; /* bottom right */
    clip_boundary[2].y = clip_area->ybot;
    clip_boundary[3].x = clip_area->xright; /* top right */
    clip_boundary[3].y = clip_area->ytop;
    clip_boundary[4] = clip_boundary[0];

    memcpy(tmp_corners, in, in_length * sizeof(gpiPoint));
    for(i = 0; i < 4; i++) {
	clip_polygon_to_boundary(tmp_corners, out, in_length, out_length, clip_boundary+i);
	memcpy(tmp_corners, out, *out_length * sizeof(gpiPoint));
	in_length = *out_length;
    }
}

/* Two routines to emulate move/vector sequence using line drawing routine. */
static unsigned int move_pos_x, move_pos_y;

void
clip_move(unsigned int x, unsigned int y)
{
    move_pos_x = x;
    move_pos_y = y;
}

void
clip_vector(unsigned int x, unsigned int y)
{
    draw_clip_line(move_pos_x, move_pos_y, x, y);
    move_pos_x = x;
    move_pos_y = y;
}

/* Common routines for setting text or line color from t_colorspec */

void
apply_pm3dcolor(struct t_colorspec *tc, const struct termentry *t)
{
    /* V5 - term->linetype(LT_BLACK) would clobber the current	*/
    /* dashtype so instead we use term->set_color(black).	*/
    static t_colorspec black = BLACK_COLORSPEC; 

    /* Replace colorspec with that of the requested line style */
    struct lp_style_type style;
    if (tc->type == TC_LINESTYLE) {
	lp_use_properties(&style, tc->lt);
	tc = &style.pm3d_color;
    }

    if (tc->type == TC_DEFAULT) {
	t->set_color(&black);
	return;
    }
    if (tc->type == TC_LT) {
	if (!monochrome_terminal)
	    t->set_color(tc);
	return;
    }
    if (tc->type == TC_RGB) {
	/* FIXME: several plausible ways for monochrome terminals to handle color request
	 * (1) Allow all color requests despite the label "monochrome"
	 * (2) Choose any color you want so long as it is black
	 * (3) Convert colors to gray scale (NTSC?)
	 */
	/* Monochrome terminals are still allowed to display rgb variable colors */
	if (monochrome_terminal && tc->value >= 0)
	    t->set_color(&black);
	else
	    t->set_color(tc);
	return;
    }
    if (!is_plot_with_palette()) {
	t->set_color(&black);
	return;
    }
    switch (tc->type) {
	case TC_Z:    set_color(cb2gray(z2cb(tc->value))); break;
	case TC_CB:   set_color(cb2gray(tc->value));       break;
	case TC_FRAC: set_color(sm_palette.positive == SMPAL_POSITIVE ?
				tc->value : 1-tc->value);
		      break;
    }
}

void
reset_textcolor(const struct t_colorspec *tc, const struct termentry *t)
{
    if (tc->type != TC_DEFAULT)
	(*t->linetype)(LT_BLACK);
}


void
default_arrow_style(struct arrow_style_type *arrow)
{
    static const struct lp_style_type tmp_lp_style = DEFAULT_LP_STYLE_TYPE;

    arrow->tag = -1;
    arrow->layer = LAYER_BACK;
    arrow->lp_properties = tmp_lp_style;
    arrow->head = 1;
    arrow->head_length = 0.0;
    arrow->head_lengthunit = first_axes;
    arrow->head_angle = 15.0;
    arrow->head_backangle = 90.0;
    arrow->headfill = AS_NOFILL;
    arrow->head_fixedsize = FALSE;
}

void
apply_head_properties(struct arrow_style_type *arrow_properties)
{
    curr_arrow_headfilled = arrow_properties->headfill;
    curr_arrow_headfixedsize = arrow_properties->head_fixedsize;
    curr_arrow_headlength = 0;
    if (arrow_properties->head_length > 0) {
	/* set head length+angle for term->arrow */
	double xtmp, ytmp;
	struct position headsize = {first_axes,graph,graph,0.,0.,0.};

	headsize.x = arrow_properties->head_length;
	headsize.scalex = arrow_properties->head_lengthunit;

	map_position_r(&headsize, &xtmp, &ytmp, "arrow");

	curr_arrow_headangle = arrow_properties->head_angle;
	curr_arrow_headbackangle = arrow_properties->head_backangle;
	curr_arrow_headlength = xtmp;
    }
}

void
free_labels(struct text_label *label)
{
struct text_label *temp;
char *master_font = label->font;

    /* Labels generated by 'plot with labels' all use the same font */
    if (master_font)
    	free(master_font);

    while (label) {
	if (label->text)
	    free(label->text);
	if (label->font && label->font != master_font)
	    free(label->font);
	temp=label->next;
	free(label);
	label = temp;
    }

}

void
get_offsets(
    struct text_label *this_label,
    struct termentry *t,
    int *htic, int *vtic)
{
    if ((this_label->lp_properties.flags & LP_SHOW_POINTS)) {
	*htic = (pointsize * t->h_tic * 0.5);
	*vtic = (pointsize * t->v_tic * 0.5);
    } else {
	*htic = 0;
	*vtic = 0;
    }
    if (is_3d_plot) {
	int htic2, vtic2;
	map3d_position_r(&(this_label->offset), &htic2, &vtic2, "get_offsets");
	*htic += htic2;
	*vtic += vtic2;
    } else {
	double htic2, vtic2;
	map_position_r(&(this_label->offset), &htic2, &vtic2, "get_offsets");
	*htic += (int)htic2;
	*vtic += (int)vtic2;
    }
}


/*
 * Write one label, with all the trimmings.
 * This routine is used for both 2D and 3D plots.
 */
void
write_label(unsigned int x, unsigned int y, struct text_label *this_label)
{
	int htic, vtic;
	int justify = JUST_TOP;	/* This was the 2D default; 3D had CENTRE */

	apply_pm3dcolor(&(this_label->textcolor),term);
	ignore_enhanced(this_label->noenhanced);

	/* The text itself */
	if (this_label->hypertext) {
	    /* Treat text as hypertext */
	    char *font = this_label->font;
	    if (font)
		term->set_font(font);
	    if (term->hypertext)
	        term->hypertext(TERM_HYPERTEXT_TOOLTIP, this_label->text);
	    if (font)
		term->set_font("");
	} else {
	    /* A normal label (always print text) */
	    get_offsets(this_label, term, &htic, &vtic);
#ifdef EAM_BOXED_TEXT
	    /* Initialize the bounding box accounting */
	    if (this_label->boxed && term->boxed_text)
		(*term->boxed_text)(x + htic, y + vtic, TEXTBOX_INIT);
#endif
	    if (this_label->rotate && (*term->text_angle) (this_label->rotate)) {
		write_multiline(x + htic, y + vtic, this_label->text,
				this_label->pos, justify, this_label->rotate,
				this_label->font);
		(*term->text_angle) (0);
	    } else {
		write_multiline(x + htic, y + vtic, this_label->text,
				this_label->pos, justify, 0, this_label->font);
	    }
	}
#ifdef EAM_BOXED_TEXT
	/* Adjust the bounding box margins */
	if (this_label->boxed && term->boxed_text)
	    (*term->boxed_text)((int)(textbox_opts.xmargin * 100.),
		(int)(textbox_opts.ymargin * 100.), TEXTBOX_MARGINS);

	if (this_label->boxed && term->boxed_text && textbox_opts.opaque) {
	    /* Blank out the box and reprint the label */
	    (*term->boxed_text)(0,0, TEXTBOX_BACKGROUNDFILL);
	    if (this_label->rotate && (*term->text_angle) (this_label->rotate)) {
		write_multiline(x + htic, y + vtic, this_label->text,
			    this_label->pos, justify, this_label->rotate,
			    this_label->font);
		(*term->text_angle) (0);
	    } else {
		write_multiline(x + htic, y + vtic, this_label->text,
			    this_label->pos, justify, 0, this_label->font);
	    }
	}

	/* Draw the bounding box - FIXME should set line properties first */
	if (this_label->boxed && term->boxed_text) {
	    if (!textbox_opts.noborder)
		(*term->boxed_text)(0,0, TEXTBOX_OUTLINE);
	    else
		(*term->boxed_text)(0,0, TEXTBOX_FINISH);
	}
#endif

	/* The associated point, if any */
	/* write_multiline() clips text to on_page; do the same for any point */
	if ((this_label->lp_properties.flags & LP_SHOW_POINTS) && on_page(x,y)) {
	    term_apply_lp_properties(&this_label->lp_properties);
	    (*term->point) (x, y, this_label->lp_properties.p_type);
	    /* the default label color is that of border */
	    term_apply_lp_properties(&border_lp);
	}

	ignore_enhanced(FALSE);
}


/* STR points to a label string, possibly with several lines separated
   by \n.  Return the number of characters in the longest line.  If
   LINES is not NULL, set *LINES to the number of lines in the
   label. */
int
label_width(const char *str, int *lines)
{
    char *lab = NULL, *s, *e;
    int mlen, len, l;

    if (!str || *str == '\0') {
	if (lines)
	    *lines = 0;
	return (0);
    }

    l = mlen = len = 0;
    lab = gp_alloc(strlen(str) + 2, "in label_width");
    strcpy(lab, str);
    strcat(lab, "\n");
    s = lab;
    while ((e = (char *) strchr(s, '\n')) != NULL) {
	*e = '\0';
	len = estimate_strlen(s);	/* = e-s ? */
	if (len > mlen)
	    mlen = len;
	if (len || l || *str == '\n')
	    l++;
	s = ++e;
    }
    /* lines = NULL => not interested - div */
    if (lines)
	*lines = l;

    free(lab);
    return (mlen);
}

