#ifndef lint
static char *RCSid() { return RCSid("$Id: gadgets.c,v 1.137.2.2 2017/06/13 05:39:24 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 "axis.h" /* For CB_AXIS */
#include "pm3d.h"
/* This file contains mainly a collection of global variables that
* 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}, FALSE,
{0,0,0,0} };
/* The graph box (terminal coordinates) calculated by boundary() or boundary3d() */
BoundingBox plot_bounds;
/* The bounding box for 3D plots prior to applying view transformations */
BoundingBox page_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;
struct linestyle_def *first_mono_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_bottom = TRUE;
/* flag for polar mode */
TBOOLEAN polar = FALSE;
TBOOLEAN inverted_raxis = 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;
/* used for filled points */
t_colorspec background_fill = BACKGROUND_COLORSPEC;
/* 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, 0, 1.0, 1.0, DEFAULT_P_CHAR, 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, 0, 1.0, 0.0, DEFAULT_P_CHAR, 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;
TBOOLEAN in_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 if 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;
TBOOLEAN continuous = TRUE;
if (points <= 1)
return;
if (p[0].x != p[points-1].x || p[0].y != p[points-1].y)
continuous = FALSE;
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);
newpath();
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);
} else {
/* Path is not continuous; make sure closepath is not called */
continuous = FALSE;
}
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;
}
/* Only call closepath if the polygon is truly closed; otherwise */
/* a spurious line connecting the start and end is generated. */
if (continuous)
closepath();
}
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 returns 0 if the whole thing is out of range */
if (clip_line(&sx, &sy, &ex, &ey))
(*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, x, y;
int 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 */
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)
{
struct termentry *t = term;
double cbval;
/* 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) {
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;
}
/* Leave unchanged. (used only by "set errorbars"??) */
if (tc->type == TC_VARIABLE)
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:
if (CB_AXIS.log)
cbval = (tc->value <= 0) ? CB_AXIS.min : axis_do_log((&CB_AXIS),tc->value);
else
cbval = tc->value;
set_color(cb2gray(cbval));
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)
{
if (tc->type != TC_DEFAULT)
term->linetype(LT_BLACK);
}
void
default_arrow_style(struct arrow_style_type *arrow)
{
static const struct lp_style_type tmp_lp_style =
{0, LT_DEFAULT, 0, DASHTYPE_SOLID, 0, 0, 1.0, 0.0, DEFAULT_P_CHAR,
DEFAULT_COLORSPEC, DEFAULT_DASHPATTERN};
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,
int *htic, int *vtic)
{
if ((this_label->lp_properties.flags & LP_SHOW_POINTS)) {
*htic = (pointsize * term->h_tic * 0.5);
*vtic = (pointsize * term->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));
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, &htic, &vtic);
#ifdef EAM_BOXED_TEXT
/* Initialize the bounding box accounting */
if ((this_label->boxed && term->boxed_text)
&& (textbox_opts.opaque || !textbox_opts.noborder))
{
(*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
if ((this_label->boxed && term->boxed_text)
&& (textbox_opts.opaque || !textbox_opts.noborder))
{
/* Adjust the bounding box margins */
(*term->boxed_text)((int)(textbox_opts.xmargin * 100.),
(int)(textbox_opts.ymargin * 100.), TEXTBOX_MARGINS);
/* Blank out the box and reprint the label */
if (textbox_opts.opaque) {
apply_pm3dcolor(&textbox_opts.fillcolor);
(*term->boxed_text)(0,0, TEXTBOX_BACKGROUNDFILL);
apply_pm3dcolor(&(this_label->textcolor));
/* Init for each of fill and border */
if (!textbox_opts.noborder)
(*term->boxed_text)(x + htic, y + vtic, TEXTBOX_INIT);
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 */
if (!textbox_opts.noborder) {
(*term->linewidth)(textbox_opts.linewidth);
apply_pm3dcolor(&textbox_opts.border_color);
(*term->boxed_text)(0,0, TEXTBOX_OUTLINE);
}
(*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);
}
/*
* Here so that it can be shared by the 2D and 3D code
*/
void
do_timelabel(unsigned int x, unsigned int y)
{
struct text_label temp = timelabel;
char str[MAX_LINE_LEN+1];
time_t now;
if (timelabel.rotate == 0 && !timelabel_bottom)
y -= term->v_char;
time(&now);
strftime(str, MAX_LINE_LEN, timelabel.text, localtime(&now));
temp.text = str;
write_label(x, y, &temp);
}