/* $Id: plctrl.c,v 1.3 2005/03/18 20:32:41 eli Exp $

	Misc. control routines, like begin, end, exit, change graphics/text
	mode, change color.  Includes some spillage from plcore.c.  If you
	don't know where it should go, put it here.  
*/

#define DEBUG

#define NEED_PLDEBUG
#include "plplotP.h"
#ifdef macintosh
#include "mac.h"
/* for plMacLibOpen prototype; used in plLibOpen */
#endif

#ifdef DJGPP			/* dos386/djgpp */
#ifdef __unix
#undef __unix
#endif
#endif

#ifdef __unix
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#endif

/* Static functions */

/* Used by any external init code to suggest a path */
MZ_DLLEXPORT
char* plplotLibDir = 0;

static void
color_set(PLINT i, U_CHAR r, U_CHAR g, U_CHAR b, char *name );

static void
strcat_delim(char *dirspec);

static int
(*exit_handler) (char *errormsg);

static void
plcmap0_def(int imin, int imax);

static void
plcmap1_def(void);

static PLFLT
value(double n1, double n2, double hue);

/* An additional hardwired location for lib files. */
/* I have no plans to change these again, ever. */

#if defined(DJGPP)
#ifndef PLLIBDEV
#define PLLIBDEV "c:/plplot/lib"
#endif

#elif defined(MSDOS)
#ifndef PLLIBDEV
#define PLLIBDEV "c:\\plplot\\lib"
#endif

#else

/* Anything else is assumed to be Unix */

#ifndef PLLIBDEV
#define PLLIBDEV "/usr/local/plplot/lib"
#endif

#endif

/*--------------------------------------------------------------------------*\
 *  Routines that deal with colors & color maps.
\*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*\
 * plcol0()
 *
 * Set color, map 0.  Argument is integer between 0 and plsc->ncol0.
\*--------------------------------------------------------------------------*/

MZ_DLLEXPORT
void
c_plcol0(PLINT icol0)
{
    if (plsc->level < 1) {
	plabort("plcol0: Please call plinit first");
	return;
    }
    if (icol0 < 0 || icol0 >= plsc->ncol0) {
	char buffer[256];
	sprintf(buffer, "plcol0: Invalid color map entry: %d", (int) icol0);
	plabort(buffer);
	return;
    }

    plsc->icol0 = icol0;
    plsc->curcolor.r = plsc->cmap0[icol0].r;
    plsc->curcolor.g = plsc->cmap0[icol0].g;
    plsc->curcolor.b = plsc->cmap0[icol0].b;

    plsc->curcmap = 0;
    plP_state(PLSTATE_COLOR0);
}

/*--------------------------------------------------------------------------*\
 * plcol1()
 *
 * Set color, map 1.  Argument is a float between 0. and 1.
\*--------------------------------------------------------------------------*/

void
c_plcol1(PLFLT col1)
{
    PLINT icol1;

    if (plsc->level < 1) {
	plabort("plcol1: Please call plinit first");
	return;
    }
    if (col1 < 0 || col1 > 1) {
	char buffer[256];
	sprintf(buffer, "plcol1: Invalid color map position: %f", (PLFLT) col1);
	plabort(buffer);
	return;
    }

    icol1 = col1 * plsc->ncol1;
    icol1 = MIN(icol1, plsc->ncol1-1);

    plsc->icol1 = icol1;
    plsc->curcolor.r = plsc->cmap1[plsc->icol1].r;
    plsc->curcolor.g = plsc->cmap1[plsc->icol1].g;
    plsc->curcolor.b = plsc->cmap1[plsc->icol1].b;

    plsc->curcmap = 1;
    plP_state(PLSTATE_COLOR1);
}

/*--------------------------------------------------------------------------*\
 * plscolbg()
 *
 * Set the background color (cmap0[0]) by 8 bit RGB value
\*--------------------------------------------------------------------------*/

MZ_DLLEXPORT
void
c_plscolbg(PLINT r, PLINT g, PLINT b)
{
    plscol0(0, r, g, b);
}

/*--------------------------------------------------------------------------*\
 * plgcolbg()
 *
 * Returns the background color (cmap0[0]) by 8 bit RGB value
\*--------------------------------------------------------------------------*/

void
c_plgcolbg(PLINT *r, PLINT *g, PLINT *b)
{
    plgcol0(0, r, g, b);
}

/*--------------------------------------------------------------------------*\
 * plscol0()
 *
 * Set a given color from color map 0 by 8 bit RGB value
 * Does not result in any additional cells to be allocated.
\*--------------------------------------------------------------------------*/

MZ_DLLEXPORT
void
c_plscol0(PLINT icol0, PLINT r, PLINT g, PLINT b)
{
    if (plsc->cmap0 == NULL)
	plscmap0n(0);

    if (icol0 < 0 || icol0 >= plsc->ncol0) {
	char buffer[256];
	sprintf(buffer, "plscol0: Illegal color table value: %d", (int) icol0);
	plabort(buffer);
	return;
    }
    if ((r < 0 || r > 255) || (g < 0 || g > 255) || (b < 0 || b > 255)) {
	char buffer[256];
	sprintf(buffer, "plscol0: Invalid RGB color: %d, %d, %d",
		(int) r, (int) g, (int) b);
	plabort(buffer);
	return;
    }

    plsc->cmap0[icol0].r = r;
    plsc->cmap0[icol0].g = g;
    plsc->cmap0[icol0].b = b;

    if (plsc->level > 0)
	plP_state(PLSTATE_CMAP0);
}

/*--------------------------------------------------------------------------*\
 * plgcol0()
 *
 * Returns 8 bit RGB values for given color from color map 0
 * Values are negative if an invalid color id is given
\*--------------------------------------------------------------------------*/

void
c_plgcol0(PLINT icol0, PLINT *r, PLINT *g, PLINT *b)
{
    if (plsc->cmap0 == NULL)
	plscmap0n(0);

    *r = -1;
    *g = -1;
    *b = -1;

    if (icol0 < 0 || icol0 > plsc->ncol0) {
	char buffer[256];
	sprintf(buffer, "plgcol0: Invalid color index: %d", (int) icol0);
	plabort(buffer);
	return;
    }

    *r = plsc->cmap0[icol0].r;
    *g = plsc->cmap0[icol0].g;
    *b = plsc->cmap0[icol0].b;

    return;
}

/*--------------------------------------------------------------------------*\
 * plscmap0()
 *
 * Set color map 0 colors by 8 bit RGB values.  This sets the entire color
 * map -- only as many colors as specified will be allocated.
\*--------------------------------------------------------------------------*/

void
c_plscmap0(PLINT *r, PLINT *g, PLINT *b, PLINT ncol0)
{
    int i;

    plscmap0n(ncol0);

    for (i = 0; i < plsc->ncol0; i++) {
	if ((r[i] < 0 || r[i] > 255) ||
	    (g[i] < 0 || g[i] > 255) ||
	    (b[i] < 0 || b[i] > 255)) {

	    char buffer[256];
	    sprintf(buffer, "plscmap0: Invalid RGB color: %d, %d, %d",
		    (int) r[i], (int) g[i], (int) b[i]);
	    plabort(buffer);
	    return;
	}

	plsc->cmap0[i].r = r[i];
	plsc->cmap0[i].g = g[i];
	plsc->cmap0[i].b = b[i];
    }

    if (plsc->level > 0)
	plP_state(PLSTATE_CMAP0);
}

/*--------------------------------------------------------------------------*\
 * plscmap1()
 *
 * Set color map 1 colors by 8 bit RGB values
 * This also sets the number of colors.
\*--------------------------------------------------------------------------*/

void
c_plscmap1(PLINT *r, PLINT *g, PLINT *b, PLINT ncol1)
{
    int i;

    plscmap1n(ncol1);

    for (i = 0; i < plsc->ncol1; i++) {
	if ((r[i] < 0 || r[i] > 255) ||
	    (g[i] < 0 || g[i] > 255) ||
	    (b[i] < 0 || b[i] > 255)) {

	    char buffer[256];
	    sprintf(buffer, "plscmap1: Invalid RGB color: %d, %d, %d",
		    (int) r[i], (int) g[i], (int) b[i]);
	    plabort(buffer);
	    return;
	}
	plsc->cmap1[i].r = r[i];
	plsc->cmap1[i].g = g[i];
	plsc->cmap1[i].b = b[i];
    }

    if (plsc->level > 0)
	plP_state(PLSTATE_CMAP1);
}

/*--------------------------------------------------------------------------*\
 * plscmap1l()
 *
 * Set color map 1 colors using a piece-wise linear relationship between
 * position in the color map (from 0 to 1) and position in HLS or RGB color
 * space.  May be called at any time.
 *
 * The idea here is to specify a number of control points that specify the
 * mapping between HLS (or RGB or CMY) and palette 1 value.  Between these
 * points, linear interpolation is used.  By mapping position in the color
 * map to function value, this gives a smooth variation of color with
 * intensity.  Any number of control points may be specified, located at
 * arbitrary positions (intensities), although typically 2 - 4 are enough.
 * Another way of stating this is that we are traversing a given number of
 * lines through HLS (or RGB) space as we move through cmap 1 entries.  The
 * control points at the minimum and maximum intensity (0 and 1) must
 * always be specified.  By adding more control points you can get more
 * variation.  One good technique for plotting functions that vary about
 * some expected average is to use an additional 2 control points in the
 * center (intensity ~= 0.5) that are the same color as the background
 * (typically white for paper output, black for crt), and same hue as the
 * boundary control points.  This allows the highs and lows to be very
 * easily distinguished.
 *
 * Each control point must specify the position in cmap 1 as well as three
 * coordinates in HLS or RGB space.  The first point MUST correspond to
 * position = 0, and the last to position = 1.  
 *
 * The hue is interpolated around the "front" of the color wheel
 * (red<->green<->blue<->red) unless the "rev" flag is set, in which case
 * interpolation proceeds around the back (reverse) side.  Specifying
 * rev=NULL is equivalent to setting rev[]=0 for every control point.
 *
 * Bounds on RGB coordinates:
 *	R,G,B		[0, 1]		magnitude
 *
 * Bounds on HLS coordinates:
 *	hue		[0, 360]	degrees
 *	lightness	[0, 1]		magnitude
 *	saturation	[0, 1]		magnitude
 *
 * The inputs are:
 *	itype		0: HLS, 1: RGB
 *	npts		number of control points
 *	pos[]		position for each control point
 *	coord1[]	first coordinate for each control point
 *	coord2[]	second coordinate for each control point
 *	coord3[]	third coordinate for each control point 
 *	rev[]		reverse flag for each control point
\*--------------------------------------------------------------------------*/

MZ_DLLEXPORT
void
c_plscmap1l(PLINT itype, PLINT npts, PLFLT *pos,
	    PLFLT *coord1, PLFLT *coord2, PLFLT *coord3, PLINT *rev)
{
    int n;
    PLFLT h, l, s, r, g, b;

    if (npts < 2) {
	plabort("plscmap1l: Must specify at least two control points");
	return;
    }

    if ( (pos[0] != 0) || (pos[npts-1] != 1)) {
	plabort("plscmap1l: First, last control points must lie on boundary");
	return;
    }

    if ( npts > PL_MAX_CMAP1CP ) {
	plabort("plscmap1l: exceeded maximum number of control points");
	return;
    }

/* Allocate if not done yet */

    if (plsc->cmap1 == NULL)
	plscmap1n(0);

/* Save control points */

    plsc->ncp1 = npts;

    for (n = 0; n < npts; n++) {

	if (itype == 0) {
	    h = coord1[n];
	    l = coord2[n];
	    s = coord3[n];
	}
	else {
	    r = coord1[n];
	    g = coord2[n];
	    b = coord3[n];
	    plRGB_HLS(r, g, b, &h, &l, &s);
	}

	plsc->cmap1cp[n].h = h;
	plsc->cmap1cp[n].l = l;
	plsc->cmap1cp[n].s = s;
	plsc->cmap1cp[n].p = pos[n];

	if (rev == NULL)
	    plsc->cmap1cp[n].rev = 0;
	else
	    plsc->cmap1cp[n].rev = rev[n];
    }

/* Calculate and set color map */

    plcmap1_calc();
}

/*--------------------------------------------------------------------------*\
 * plcmap1_calc()
 *
 * Bin up cmap 1 space and assign colors to make inverse mapping easy.
 * Always do interpolation in HLS space.
\*--------------------------------------------------------------------------*/

void
plcmap1_calc(void)
{
    int i, n;
    PLFLT delta, dp, dh, dl, ds;
    PLFLT h, l, s, p, r, g, b;

/* Loop over all control point pairs */

    for (n = 0; n < plsc->ncp1-1; n++) {

	if ( plsc->cmap1cp[n].p == plsc->cmap1cp[n+1].p )
	    continue;

    /* Differences in p, h, l, s between ctrl pts */

	dp = plsc->cmap1cp[n+1].p - plsc->cmap1cp[n].p;
	dh = plsc->cmap1cp[n+1].h - plsc->cmap1cp[n].h;
	dl = plsc->cmap1cp[n+1].l - plsc->cmap1cp[n].l;
	ds = plsc->cmap1cp[n+1].s - plsc->cmap1cp[n].s;

    /* Adjust dh if we are to go around "the back side" */

	if (plsc->cmap1cp[n].rev)
	    dh = (dh > 0) ? dh-360 : dh+360;

    /* Loop over all color cells.  Only interested in cells located (in */
    /* cmap1 space)  between n_th and n+1_th control points */

	for (i = 0; i < plsc->ncol1; i++) {
	    p = (double) i / (plsc->ncol1 - 1.0);
	    if ( (p < plsc->cmap1cp[n].p) ||
		 (p > plsc->cmap1cp[n+1].p) )
		continue;

	/* Interpolate based on position of color cell in cmap1 space */

	    delta = (p - plsc->cmap1cp[n].p) / dp;

	/* Linearly interpolate to get color cell h, l, s values */

	    h = plsc->cmap1cp[n].h + dh * delta;
	    l = plsc->cmap1cp[n].l + dl * delta;
	    s = plsc->cmap1cp[n].s + ds * delta;

	    while (h >= 360.)
		h -= 360.;

	    while (h < 0.)
		h += 360.;

	    plHLS_RGB(h, l, s, &r, &g, &b);

	    plsc->cmap1[i].r = MAX(0, MIN(255, (int) (256. * r)));
	    plsc->cmap1[i].g = MAX(0, MIN(255, (int) (256. * g)));
	    plsc->cmap1[i].b = MAX(0, MIN(255, (int) (256. * b)));
	}
    }

    if (plsc->level > 0)
	plP_state(PLSTATE_CMAP1);
}

/*--------------------------------------------------------------------------*\
 * plscmap0n()
 *
 * Set number of colors in cmap 0, (re-)allocate cmap 0, and fill with
 * default values for those colors not previously allocated (and less
 * than index 15, after that you just get grey).
 *
 * The driver is not guaranteed to support all of these.
\*--------------------------------------------------------------------------*/

void
c_plscmap0n(PLINT ncol0)
{
    int ncol, size, imin, imax;

/* No change */

    if (ncol0 > 0 && plsc->ncol0 == ncol0)
	return;

/* Handle all possible startup conditions */

    if (plsc->ncol0 <= 0 && ncol0 <= 0)
	ncol = 16;
    else if (ncol0 <= 0)
	ncol = plsc->ncol0;
    else
        ncol = ncol0;

    imax = ncol-1;
    size = ncol * sizeof(PLColor);

/* Allocate the space */

    if (plsc->cmap0 == NULL) {
	plsc->cmap0 = (PLColor *) calloc(1, size);
	imin = 0;
    }
    else {
	plsc->cmap0 = (PLColor *) realloc(plsc->cmap0, size);
	imin = plsc->ncol0;
    }

/* Fill in default entries */

    plsc->ncol0 = ncol;
    plcmap0_def(imin, imax);
}

/*--------------------------------------------------------------------------*\
 * plscmap1n()
 *
 * Set number of colors in cmap 1, (re-)allocate cmap 1, and set default
 * values if this is the first allocation.
 *
 * Note that the driver is allowed to disregard this number.
 * In particular, most use fewer than we use internally.
\*--------------------------------------------------------------------------*/

MZ_DLLEXPORT
void
c_plscmap1n(PLINT ncol1)
{
    int ncol, size;

/* No change */

    if (ncol1 > 0 && plsc->ncol1 == ncol1)
	return;

/* Handle all possible startup conditions */

    if (plsc->ncol1 <= 0 && ncol1 <= 0)
	ncol = 128;
    else if (ncol1 <= 0)
	ncol = plsc->ncol1;
    else
        ncol = ncol1;

    size = ncol * sizeof(PLColor);

/* Allocate the space */

    if (plsc->ncol1 > 0) 
	plsc->cmap1 = (PLColor *) realloc(plsc->cmap1, size);
    else 
	plsc->cmap1 = (PLColor *) calloc(ncol, sizeof(PLColor));

/* Fill in default entries */

    plsc->ncol1 = ncol;
    if (plsc->ncp1 == 0)
	plcmap1_def();
    else
	plcmap1_calc();
}

/*--------------------------------------------------------------------------*\
 * color_set()
 *
 * Initializes color table entry by RGB values.
\*--------------------------------------------------------------------------*/

static void
color_set(PLINT i, U_CHAR r, U_CHAR g, U_CHAR b, char *name )
{
    plsc->cmap0[i].r = r;
    plsc->cmap0[i].g = g;
    plsc->cmap0[i].b = b;
    plsc->cmap0[i].name = name;
}

/*--------------------------------------------------------------------------*\
 * plcmap0_def()
 *
 * Initializes specified color map 0 color entry to its default.
 *
 * Initial RGB values for color map 0 taken from X11R6 
 * (XFree86-3.3.6) X-windows 
 * rgb.txt file, and may not accurately represent the described colors on 
 * all systems.
\*--------------------------------------------------------------------------*/

#define color_def(i, r, g, b, n) \
if (i >= imin && i <= imax) color_set(i, r, g, b, n);

static void
plcmap0_def(int imin, int imax)
{
    int i;

    color_def(0,    0,   0,   0, "black" );	/* black */
    color_def(1,  255,   0,   0, "red");	/* red */
    color_def(2,  255, 255,   0, "yellow" );	/* yellow */
    color_def(3,    0, 255,   0, "green" );	/* green */
    color_def(4,  127, 255, 212, "aquamarine" );	/* aquamarine */
    color_def(5,  255, 192, 203, "pink" );	/* pink */
    color_def(6,  245, 222, 179, "wheat" );	/* wheat */
    color_def(7,  190, 190, 190, "grey" );	/* grey */
    color_def(8,  165,  42,  42, "brown" );	/* brown */
    color_def(9,    0,   0, 255, "blue" );	/* blue */
    color_def(10, 138,  43, 226, "BlueViolet" );	/* Blue Violet */
    color_def(11,   0, 255, 255, "cyan" );	/* cyan */
    color_def(12,  64, 224, 208, "turquoise" );	/* turquoise */
    color_def(13, 255,   0, 255, "magenta" );	/* magenta */
    color_def(14, 250, 128, 114, "salmon" );	/* salmon */
    color_def(15, 255, 255, 255, "white" );	/* white */

/* Any others are just arbitrarily set */

    for (i = 16; i <= imax; i++)
	color_def(i, 255, 0, 0, "red"); 	/* red */
}

/*--------------------------------------------------------------------------*\
 * plcmap1_def()
 *
 * Initializes color map 1.
 *
 * The default initialization uses 6 control points in HLS space, the inner
 * ones being very close to one of the vertices of the HLS double cone.  The
 * vertex used (black or white) is chosen to be the closer to the background
 * color.  The 6 points were chosen over the older 4 points in order to make 
 * weaker structures more easily visible, and give more control through the
 * palette editor.  If you don't like these settings.. change them!
\*--------------------------------------------------------------------------*/

static void
plcmap1_def(void)
{
    PLFLT i[6], h[6], l[6], s[6], midpt = 0., vertex = 0.;

/* Positions of control points */

    i[0] = 0;		/* left boundary */
    i[1] = 0.44;	/* a little left of center */
    i[2] = 0.50;	/* at center */
    i[3] = 0.50;	/* at center */
    i[4] = 0.56;	/* a little right of center */
    i[5] = 1;		/* right boundary */

/* For center control points, pick black or white, whichever is closer to bg */
/* Be carefult to pick just short of top or bottom else hue info is lost */

    if (plsc->cmap0 != NULL)
	vertex = ((PLFLT) plsc->cmap0[0].r +
		  (PLFLT) plsc->cmap0[0].g +
		  (PLFLT) plsc->cmap0[0].b) / 3. / 255.;

    if (vertex < 0.5) {
	vertex = 0.01;
	midpt  = 0.10;
    } else {
	vertex = 0.99;
	midpt  = 0.90;
    }

/* Set hue */

    h[0] = 260;		/* low: blue-violet */
    h[1] = 260;		/* only change as we go over vertex */
    h[2] = 260;		/* only change as we go over vertex */
    h[3] = 0;		/* high: red */
    h[4] = 0;		/* high: red */
    h[5] = 0;		/* keep fixed */

/* Set lightness */

    l[0] = 0.5;		/* low */
    l[1] = midpt;	/* midpoint value */
    l[2] = vertex;	/* bg */
    l[3] = vertex;	/* bg */
    l[4] = midpt;	/* midpoint value */
    l[5] = 0.5;		/* high */

/* Set saturation -- keep at maximum */

    s[0] = 1;
    s[1] = 1;
    s[2] = 1;
    s[3] = 1;
    s[4] = 1;
    s[5] = 1;

    c_plscmap1l(0, 6, i, h, l, s, NULL);
}

/*--------------------------------------------------------------------------*\
 * plscolor()
 *
 * Used to globally turn color output on/off
\*--------------------------------------------------------------------------*/

void
c_plscolor(PLINT color)
{
    plsc->colorset = 1;
    plsc->color = color;
}

/*--------------------------------------------------------------------------*\
 * plrgb()
 *
 * Set line color by red, green, blue from  0. to 1.
 * Do NOT use this.  Only retained for backward compatibility
\*--------------------------------------------------------------------------*/

void
c_plrgb(PLFLT r, PLFLT g, PLFLT b)
{
    if (plsc->level < 1) {
	plabort("plrgb: Please call plinit first");
	return;
    }

    plsc->icol0 = PL_RGB_COLOR;
    plsc->curcolor.r = MAX(0, MIN(255, (int) (256. * r)));
    plsc->curcolor.g = MAX(0, MIN(255, (int) (256. * g)));
    plsc->curcolor.b = MAX(0, MIN(255, (int) (256. * b)));

    plsc->curcmap = 0;
    plP_state(PLSTATE_COLOR0);
}

/*--------------------------------------------------------------------------*\
 * plrgb1()
 *
 * Set line color by 8 bit RGB values.
 * Do NOT use this.  Only retained for backward compatibility
\*--------------------------------------------------------------------------*/

void
c_plrgb1(PLINT r, PLINT g, PLINT b)
{
    if (plsc->level < 1) {
	plabort("plrgb1: Please call plinit first");
	return;
    }
    if ((r < 0 || r > 255) || (g < 0 || g > 255) || (b < 0 || b > 255)) {
	plabort("plrgb1: Invalid color");
	return;
    }

    plsc->icol0 = PL_RGB_COLOR;
    plsc->curcolor.r = r;
    plsc->curcolor.g = g;
    plsc->curcolor.b = b;

    plsc->curcmap = 0;
    plP_state(PLSTATE_COLOR0);
}

/*--------------------------------------------------------------------------*\
 * void plhls()
 *
 * Set current color by hue, lightness, and saturation.
 * Convert hls color coordinates to rgb, then call plrgb.
 * Do NOT use this.  Only retained for backward compatibility
\*--------------------------------------------------------------------------*/

void
c_plhls(PLFLT h, PLFLT l, PLFLT s)
{
    PLFLT r, g, b;

    plHLS_RGB(h, l, s, &r, &g, &b);
    plrgb(r, g, b);
}

/*--------------------------------------------------------------------------*\
 * void value()
 *
 * Auxiliary function used by plHLS_RGB().
\*--------------------------------------------------------------------------*/

static PLFLT
value(double n1, double n2, double hue)
{
    PLFLT val;

    while (hue >= 360.)
	hue -= 360.;
    while (hue < 0.)
	hue += 360.;

    if (hue < 60.)
	val = n1 + (n2 - n1) * hue / 60.;
    else if (hue < 180.)
	val = n2;
    else if (hue < 240.)
	val = n1 + (n2 - n1) * (240. - hue) / 60.;
    else
	val = n1;

    return (val);
}

/*--------------------------------------------------------------------------*\
 * void plHLS_RGB()
 *
 * Convert HLS color to RGB color.
 * Bounds on HLS (input):
 *	hue		[0., 360.]	degrees
 *	lightness	[0., 1.]	magnitude
 *	saturation	[0., 1.]	magnitude
 *
 * Hue is always mapped onto the interval [0., 360.] regardless of input.
 * Bounds on RGB (output) is always [0., 1.].  Convert to RGB color values
 * by multiplying by 2**nbits (nbits typically 8).
\*--------------------------------------------------------------------------*/

void
plHLS_RGB(PLFLT h, PLFLT l, PLFLT s, PLFLT *p_r, PLFLT *p_g, PLFLT *p_b)
{
    PLFLT m1, m2;

    if (l <= .5)
	m2 = l * (s + 1.);
    else
	m2 = l + s - l * s;

    m1 = 2 * l - m2;

    *p_r = value(m1, m2, h + 120.);
    *p_g = value(m1, m2, h);
    *p_b = value(m1, m2, h - 120.);
}

/*--------------------------------------------------------------------------*\
 * void plRGB_HLS()
 *
 * Convert RGB color to HLS color.
 * Bounds on RGB (input) is always [0., 1.].  
 * Bounds on HLS (output):
 *	hue		[0., 360.]	degrees
 *	lightness	[0., 1.]	magnitude
 *	saturation	[0., 1.]	magnitude
\*--------------------------------------------------------------------------*/

void
plRGB_HLS(PLFLT r, PLFLT g, PLFLT b, PLFLT *p_h, PLFLT *p_l, PLFLT *p_s)
{
    PLFLT h, l, s, d, rc, gc, bc, rgb_min, rgb_max;

    rgb_min = MIN( r, MIN( g, b ));
    rgb_max = MAX( r, MAX( g, b ));

    l = (rgb_min+rgb_max) / 2.0;

    if (rgb_min == rgb_max) {
	s = 0;
	h = 0;
    } 
    else {
	d = rgb_max - rgb_min;
	if (l < 0.5)
	    s = 0.5 * d / l;
	else 
	    s = 0.5* d / (1.-l);

	rc = (rgb_max-r) / d;
	gc = (rgb_max-g) / d;
	bc = (rgb_max-b) / d;

	if (r == rgb_max)
	    h = bc-gc;
	else if (g == rgb_max)
	    h = rc-bc+2;
	else
	    h = gc-rc-2;

	h = h*60;
	if (h <  0)
	    h = h+360;
	else if (h >= 360)
	    h = h-360;
    }
    *p_h = h;
    *p_l = l;
    *p_s = s;
}

/*--------------------------------------------------------------------------*\
 * A grab-bag of various control routines.
\*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*\
 * void plwarn()
 *
 * A handy way to issue warnings, if need be.
\*--------------------------------------------------------------------------*/

void
plwarn(char *errormsg)
{
    int was_gfx = 0;

    if (plsc->graphx == 1) {
	was_gfx = 1;
	pltext();
    }

    fprintf(stderr, "\n*** PLPLOT WARNING ***\n");
    if (*errormsg != '\0')
	fprintf(stderr, "%s\n", errormsg);

    if (was_gfx == 1)
	plgra();
}

/*--------------------------------------------------------------------------*\
 * void plabort()
 *
 * Much the same as plwarn(), but appends ", aborting operation" to the
 * error message.  Helps to keep source code uncluttered and provides a
 * convention for error aborts.
\*--------------------------------------------------------------------------*/

void
plabort(char *errormsg)
{
    if (plsc->errcode != NULL)
	*(plsc->errcode) = 1;

    if (plsc->errmsg != NULL) {
	sprintf(plsc->errmsg, "\n*** PLPLOT ERROR ***\n");
	if (*errormsg != '\0')
	    sprintf(plsc->errmsg, "%s, aborting operation\n", errormsg);

    } else {
	int was_gfx = 0;

	if (plsc->graphx == 1) {
	    was_gfx = 1;
	    pltext();
	}

	fprintf(stderr, "\n*** PLPLOT ERROR ***\n");
	if (*errormsg != '\0')
	    fprintf(stderr, "%s, aborting operation\n", errormsg);

	if (was_gfx == 1)
	    plgra();
    }
}

/*--------------------------------------------------------------------------*\
 * void plexit()
 *
 * In case of an abort this routine is called.  It just prints out an error
 * message and tries to clean up as much as possible.  It's best to turn
 * off pause and then restore previous setting before returning.
 *
 * If cleanup needs to be done in the main program, the user should write
 * his/her own exit handler and pass it in via plsexit().  This function
 * should should either call plend() before exiting, or simply return.
\*--------------------------------------------------------------------------*/

void
plexit(char *errormsg)
{
    int status = 1;

    if (exit_handler != NULL)
	status = (*exit_handler)(errormsg);

    plsc->nopause = 1;
    if (*errormsg != '\0') {
	fprintf(stderr, "\n*** PLPLOT ERROR ***\n");
	fprintf(stderr, "%s\n", errormsg);
    }
    plend();

    fprintf(stderr, "Program aborted\n");
    exit(status);
}

/*--------------------------------------------------------------------------*\
 * void plsexit()
 *
 * Sets an optional user exit handler.
\*--------------------------------------------------------------------------*/

void
plsexit(int (*handler) (char *))
{
    exit_handler = handler;
}

/*--------------------------------------------------------------------------*\
 * void plgra()
 *
 * Switches to graphics screen.  
 *
 * Here and in pltext() it's a good idea to return silently if plinit()
 * hasn't yet been called, since plwarn() calls pltext() and plgra(), and
 * plwarn() may be called at any time.
\*--------------------------------------------------------------------------*/

void
c_plgra(void)
{
    if (plsc->level > 0)
	plP_esc(PLESC_GRAPH, NULL);
}

void
c_plxormod(PLINT mode, PLINT *status)	/* xor mode */
{
  static int ostate = 0;

  if (!plsc->dev_xor) {
    *status = 0;
    return;
  }

  if (plsc->level > 0) {
    plP_esc(PLESC_XORMOD, &mode);
    if (mode) {
      ostate = plsc->plbuf_write;
      plsc->plbuf_write = 0;
    } else
      plsc->plbuf_write = ostate;
  } 
  *status = 1;
}

/*--------------------------------------------------------------------------*\
 * void pltext()
 *
 * Switches to text screen.
\*--------------------------------------------------------------------------*/

void
c_pltext(void)
{
    if (plsc->level > 0)
	plP_esc(PLESC_TEXT, NULL);
}

/*--------------------------------------------------------------------------*\
 * void pl_cmd()
 *
 * Front-end to driver escape function.
 * In principle this can be used to pass just about anything directly
 * to the driver.
\*--------------------------------------------------------------------------*/

void
pl_cmd(PLINT op, void *ptr)
{
    plP_esc(op, ptr);
}

/*--------------------------------------------------------------------------*\
 * char *plFindCommand
 *
 * Looks for the specified executable file.  Search path:
 *	PLPLOT_BIN_ENV = $(PLPLOT_BIN)
 *	current directory
 *	PLPLOT_HOME_ENV/bin = $(PLPLOT_HOME)/bin
 *	BIN_DIR
 *
 * The caller must free the returned pointer (points to malloc'ed memory)
 * when finished with it.
\*--------------------------------------------------------------------------*/

char *
plFindCommand(char *fn)
{
    char *fs = NULL, *dn;

/* PLPLOT_BIN_ENV = $(PLPLOT_BIN) */

#if defined(PLPLOT_BIN_ENV)
    if ((dn = getenv(PLPLOT_BIN_ENV)) != NULL) {
        plGetName(dn, "", fn, &fs);
        if ( ! plFindName(fs))
            return fs;
        fprintf(stderr, PLPLOT_BIN_ENV"=\"%s\"\n", dn); /* what IS set? */
    }
#endif  /* PLPLOT_BIN_ENV */

/* Current directory */

    plGetName(".", "", fn, &fs);
    if ( ! plFindName(fs))
	return fs;

/* PLPLOT_HOME_ENV/bin = $(PLPLOT_HOME)/bin */

#if defined(PLPLOT_HOME_ENV)
    if ((dn = getenv(PLPLOT_HOME_ENV)) != NULL) {
        plGetName(dn, "bin", fn, &fs);
        if ( ! plFindName(fs))
            return fs;
        fprintf(stderr, PLPLOT_HOME_ENV"=\"%s\"\n",dn); /* what IS set? */
    }
#endif  /* PLPLOT_HOME_ENV */

/* BIN_DIR */

#if defined (BIN_DIR)
    plGetName(BIN_DIR, "", fn, &fs);
    if ( ! plFindName(fs))
	return fs;
#endif

/* Crapped out */

    free_mem(fs);
    fprintf(stderr, "plFindCommand: cannot locate command: %s\n", fn);
#if defined (BIN_DIR)
    fprintf(stderr, "bin dir=\"" BIN_DIR "\"\n" );      /* what WAS set? */
#endif  /* BIN_DIR */
    return NULL;
}

/*--------------------------------------------------------------------------*\
 * FILE *plLibOpen(fn)
 *
 * Return file pointer to lib file.
 * Locations checked:
 *	PLPLOT_LIB_ENV = $(PLPLOT_LIB)
 *	current directory
 *	PLPLOT_HOME_ENV/lib = $(PLPLOT_HOME)/lib
 *	DATA_DIR
 *	PLLIBDEV
\*--------------------------------------------------------------------------*/

FILE *
plLibOpen(char *fn)
{
    FILE *ret = NULL;
    
    PDFstrm *pdfs = plLibOpenPdfstrm(fn);
    if (pdfs == NULL) {
        return NULL;
    }
    if (pdfs->file != NULL) {
        ret = pdfs->file;
	pdfs->file = NULL;
    }
    pdf_close(pdfs);
    return ret;
}

PDFstrm *
plLibOpenPdfstrm(char *fn)
{
    PDFstrm *file;
    char *fs = NULL, *dn = NULL;

/****	search PLPLOT_LIB_ENV = $(PLPLOT_LIB)	****/

#if defined(PLPLOT_LIB_ENV)
    if ((dn = getenv(PLPLOT_LIB_ENV)) != NULL) {
        plGetName(dn, "", fn, &fs);

        if ((file = pdf_fopen(fs, "rb")) != NULL)
            goto done;

        fprintf(stderr, PLPLOT_LIB_ENV"=\"%s\"\n", dn); /* what IS set? */
    }
#endif  /* PLPLOT_LIB_ENV */

/****	search current directory	****/

    if ((file = pdf_fopen(fn, "rb")) != NULL)
        goto done;

/****	search PLPLOT_HOME_ENV/lib = $(PLPLOT_HOME)/lib	****/

#if defined (PLPLOT_HOME_ENV)
    if ((dn = getenv(PLPLOT_HOME_ENV)) != NULL) {
        plGetName(dn, "lib", fn, &fs);

        if ((file = pdf_fopen(fs, "rb")) != NULL)
            goto done;
        fprintf(stderr, PLPLOT_HOME_ENV"=\"%s\"\n",dn); /* what IS set? */
    }
#endif  /* PLPLOT_HOME_ENV/lib */

/**** 	search installed location	****/

#if defined (DATA_DIR)
    plGetName(DATA_DIR, "", fn, &fs);

    if ((file = pdf_fopen(fs, "rb")) != NULL)
        goto done;
#endif  /* DATA_DIR */

/**** 	search hardwired location	****/

#ifdef PLLIBDEV
    plGetName(PLLIBDEV, "", fn, &fs);

    if ((file = pdf_fopen(fs, "rb")) != NULL)
	goto done;
#endif	/* PLLIBDEV */

#ifdef macintosh
    file = plMacLibOpen(fn);
    if (file != NULL)
        goto done;
#endif /* macintosh */

    if (plplotLibDir != NULL) {
	plGetName(plplotLibDir, "", fn, &fs);
	if ((file = pdf_fopen(fs, "rb")) != NULL)
	    goto done;

    }
    
/**** 	not found, give up 	****/

    return NULL;

 done:
    free_mem(fs);
    return (file);
}

/*--------------------------------------------------------------------------*\
 * int plFindName
 *
 * Authors: Paul Dubois (LLNL), others?
 * This function is in the public domain.
 *
 * Given a pathname, determine if it is a symbolic link.  If so, continue
 * searching to the ultimate terminus - there may be more than one link.
 * Use the error value to determine when the terminus is reached, and to
 * determine if the pathname really exists.  Then stat it to determine
 * whether it's executable.  Return 0 for an executable, errno otherwise.
 * Note that 'p' _must_ have at least one '/' character - it does by
 * construction in this program.  The contents of the array pointed to by
 * 'p' are changed to the actual pathname if findname is successful.
 *
 * This function is only defined under Unix for now.
\*--------------------------------------------------------------------------*/

#ifdef do_not_do // changed, this is unnessisary in this build anyway
int 
plFindName(char *p)
{
    int n;
    char buf[1024], *cp;
    extern int errno;
    struct stat sbuf;

    pldebug("plFindName", "Trying to find %s\n", p);
    while ((n = readlink(p, buf, 1024)) > 0) {
	pldebug("plFindName", "Readlink read %d chars at: %s\n", n, p);
	if (buf[0] == '/') {
	/* Link is an absolute path */

	    strncpy(p, buf, n);
	    p[n] = '\0';
	    pldebug("plFindName", "Link is absolute: %s\n", p);
	}
	else {
	/* Link is relative to its directory; make it absolute */

	    cp = 1 + strrchr(p, '/');
	    strncpy(cp, buf, n);
	    cp[n] = '\0';
	    pldebug("plFindName",
		    "Link is relative: %s\n\tTotal path:%s\n", cp, p);
	}
    }

/* This macro not defined on the NEC SX-3 */

#ifdef SX
#define S_ISREG(mode)   (mode & S_IFREG)
#endif

/* SGI machines return ENXIO instead of EINVAL Dubois 11/92 */

    if (errno == EINVAL || errno == ENXIO) {
	pldebug("plFindName", "%s may be the one...\n", p);
	if ((stat(p, &sbuf) == 0) && S_ISREG(sbuf.st_mode)) {
	    pldebug("plFindName", "%s is a regular file\n", p);
	    return (access(p, X_OK));
	}
    }
    pldebug("plFindName", "%s found but is not executable\n", p);
    return (errno ? errno : -1);
}

#else
int 
plFindName(char *p)
{
    return 1;
}
#endif

/*--------------------------------------------------------------------------*\
 * void plGetName()
 *
 * Gets search name for file by concatenating the dir, subdir, and file
 * name, allocating memory as needed.  The appropriate delimiter is added
 * after the dir specification as necessary.  The caller is responsible
 * for freeing the malloc'ed memory.
\*--------------------------------------------------------------------------*/

void
plGetName(char *dir, char *subdir, char *filename, char **filespec)
{
    int lfilespec;

/* Malloc space for filespec */

    free_mem(*filespec);
    lfilespec = 10;
    lfilespec = strlen(dir) + strlen(subdir) + strlen(filename) + 10;
    *filespec = (char *) malloc(lfilespec);

    strcpy(*filespec, dir);

    if (*subdir != '\0') {
	strcat_delim(*filespec);
	strcat(*filespec, subdir);
    }
    if (*filename != '\0') {
	strcat_delim(*filespec);
	strcat(*filespec, filename);
    }
}

/*--------------------------------------------------------------------------*\
 * void strcat_delim()
 *
 * Append path name deliminator if necessary (does not add one if one's
 * there already, or if dealing with a colon-terminated device name).
\*--------------------------------------------------------------------------*/

static void
strcat_delim(char *dirspec)
{
    int ldirspec = strlen(dirspec);
#if defined (MSDOS)
    if (dirspec[ldirspec-1] != '\\')
	strcat(dirspec, "\\");
#elif defined (macintosh)
    if (dirspec[ldirspec-1] != ':')
        strcat(dirspec, ":");
#else           /* unix is the default */
    if (dirspec[ldirspec-1] != '/')
	strcat(dirspec, "/");
#endif
}

/*--------------------------------------------------------------------------*\
 * plcol_interp()
 *
 * Initializes device cmap 1 entry by interpolation from pls->cmap1
 * entries.  Returned PLColor is supposed to represent the i_th color
 * out of a total of ncol colors in the current color scheme.
\*--------------------------------------------------------------------------*/

void
plcol_interp(PLStream *pls, PLColor *newcolor, int i, int ncol)
{
    PLFLT x, delta;
    int il, ir;

    x = (double) (i * (pls->ncol1-1)) / (double) (ncol-1);
    il = x;
    ir = il + 1;
    delta = x - il;

    if (ir > pls->ncol1 || il < 0)
	fprintf(stderr, "Invalid color\n");

    else if (ir == pls->ncol1 || (delta == 0.)) {
	newcolor->r = pls->cmap1[il].r;
	newcolor->g = pls->cmap1[il].g;
	newcolor->b = pls->cmap1[il].b;
    }
    else {
	newcolor->r = (1.-delta) * pls->cmap1[il].r + delta * pls->cmap1[ir].r;
	newcolor->g = (1.-delta) * pls->cmap1[il].g + delta * pls->cmap1[ir].g;
	newcolor->b = (1.-delta) * pls->cmap1[il].b + delta * pls->cmap1[ir].b;
    }
}

/*--------------------------------------------------------------------------*\
 * plOpenFile()
 *
 * Opens file for output, prompting if not set.
 * Prints extra newline at end to make output look better in batch runs.
 * A file name of "-" indicates output to stdout.
\*--------------------------------------------------------------------------*/

#define MAX_NUM_TRIES	10
void
plOpenFile(PLStream *pls)
{
    int i = 0, count = 0;
    size_t len;
    char line[256];

    while (pls->OutFile == NULL) {

/* Setting pls->FileName = NULL forces creation of a new family member */
/* You should also free the memory associated with it if you do this */

	if (pls->family && pls->BaseName != NULL)
	    plP_getmember(pls);

/* Prompt if filename still not known */

	if (pls->FileName == NULL) {
	    do {
		fprintf(stdout, "Enter graphics output file name: ");
		fgets(line, sizeof(line), stdin);
		len = strlen(line);
		if (len)
		    len--;
		line[len] = '\0';	/* strip new-line */
		count++;		/* count zero entries */
	    } while (!len && count < MAX_NUM_TRIES);
	    plP_sfnam(pls, line);
	}

/* If name is "-", send to stdout */

	if ( ! strcmp(pls->FileName, "-")) {
	    pls->OutFile = stdout;
	    pls->output_type = 1;
	    break;
	}

/* Need this here again, for prompted family initialization */

	if (pls->family && pls->BaseName != NULL) 
	    plP_getmember(pls);

	if (i++ > 10)
	    plexit("Too many tries.");

	if ((pls->OutFile = fopen(pls->FileName, "wb+")) == NULL) 
	    fprintf(stdout, "Can't open %s.\n", pls->FileName);
	else
	  {
//	    fprintf(stderr, "Opened %s\n", pls->FileName);
// 	    this is not needed
	  }
    }
}

/*--------------------------------------------------------------------------*\
 * plP_getmember()
 *
 * Sets up next file member name (in pls->FileName), but does not open it.
\*--------------------------------------------------------------------------*/

void
plP_getmember(PLStream *pls)
{
    char tmp[256];

    if (pls->FileName == NULL)
	pls->FileName = (char *) malloc(10 + strlen(pls->BaseName));

    sprintf(tmp, "%s.%%0%1ii", pls->BaseName, (int) pls->fflen);
    sprintf(pls->FileName, tmp, pls->member);
}

/*--------------------------------------------------------------------------*\
 * plP_sfnam()
 *
 * Sets up file name & family stem name.
 * Reserve some extra space (5 chars) to hold an optional member number.
\*--------------------------------------------------------------------------*/

void
plP_sfnam(PLStream *pls, const char *fnam)
{
    pls->OutFile = NULL;

    if (pls->FileName != NULL)
	free((void *) pls->FileName);

    pls->FileName = (char *) malloc(10 + strlen(fnam));

    strcpy(pls->FileName, fnam);

    if (pls->BaseName != NULL)
	free((void *) pls->BaseName);

    pls->BaseName = (char *) malloc(10 + strlen(fnam));

    strcpy(pls->BaseName, fnam);
}

/*--------------------------------------------------------------------------*\
 * plFamInit()
 *
 * Initializes family file parameters.
\*--------------------------------------------------------------------------*/

void
plFamInit(PLStream *pls)
{
    if (pls->family) {
	pls->bytecnt = 0;
	if ( ! pls->member)
	    pls->member = 1;
	if ( ! pls->finc)
	    pls->finc = 1;
	if ( ! pls->fflen)
	    pls->fflen = 1;
	if ( ! pls->bytemax)
	    pls->bytemax = PL_FILESIZE_KB * 1000;
    }
}

/*--------------------------------------------------------------------------*\
 * plGetFam()
 *
 * Starts new member file of family file set if necessary.
 *
 * Note each member file is a complete graphics file (can be printed
 * individually), although 'plrender' will treat a family as a single
 * logical file if given the family name instead of the member name.
\*--------------------------------------------------------------------------*/

void
plGetFam(PLStream *pls)
{
    PLFLT xpmm_loc, ypmm_loc;
    if (pls->family) {
	if (pls->bytecnt > pls->bytemax || pls->famadv) {
	    plP_tidy();
	    pls->member += pls->finc;
	    pls->famadv = 0;
	    plP_init();
	   /* Apply compensating factor to original xpmm and ypmm so that 
	    * character aspect ratio is preserved when overall aspect ratio
	    * is changed. */
	    plP_gpixmm(&xpmm_loc, &ypmm_loc);
	    plP_setpxl(xpmm_loc*plsc->caspfactor, ypmm_loc/plsc->caspfactor); 
	    return;
	}
    }
}

/*--------------------------------------------------------------------------*\
 * plRotPhy()
 *
 * Rotates physical coordinates if necessary for given orientation.
 * Each time orient is incremented, the plot is rotated 90 deg clockwise.
 * Note: this is now used only to rotate by 90 degrees for devices that
 * expect portrait mode.
\*--------------------------------------------------------------------------*/

void
plRotPhy(PLINT orient, PLINT xmin, PLINT ymin, PLINT xmax, PLINT ymax,
	 int *px, int *py)
{
    int x, y;

    x = *px;
    y = *py;

    switch (orient%4) {

    case 1:
	*px = xmin + (y - ymin);
	*py = ymin + (xmax - x);
	break;

    case 2:
	*px = xmin + (xmax - x);
	*py = ymin + (ymax - y);
	break;

    case 3:
	*px = xmin + (ymax - y);
	*py = ymin + (x - xmin);
	break;

    default:
	break;			/* do nothing */
    }
}

/*--------------------------------------------------------------------------*\
 * plAllocDev()
 *
 * Allocates a standard PLDev structure for device-specific data, stores
 * the address in pls->dev, and returns the address as well.
\*--------------------------------------------------------------------------*/

PLDev *
plAllocDev(PLStream *pls)
{
    if (pls->dev != NULL)
	free((void *) pls->dev);

    pls->dev = calloc(1, (size_t) sizeof(PLDev));
    if (pls->dev == NULL)
	plexit("plAllocDev: cannot allocate memory\n");

    return (PLDev *) pls->dev;
}

/*--------------------------------------------------------------------------*\
 * plGinInit()
 *
 * Just fills in the PLGraphicsIn with appropriate initial values.
\*--------------------------------------------------------------------------*/

void
plGinInit(PLGraphicsIn *gin)
{
    gin->type = 0;
    gin->state = 0;
    gin->keysym = 0;
    gin->button = 0;
    gin->string[0] = '\0';
    gin->pX = gin->pY = -1;
    gin->dX = gin->dY = 0.;
    gin->wX = gin->wY = 0.;
}

/*--------------------------------------------------------------------------*\
 * plGetInt()
 *
 * Prompts human to input an integer in response to given message.
\*--------------------------------------------------------------------------*/

PLINT
plGetInt(char *s)
{
    int m;
    int i = 0;
    char line[256];

    while (i++ < 10) {
	fprintf(stdout, s);
	fgets(line, sizeof(line), stdin);
#ifdef MSDOS
	m = atoi(line);
	return (m);
#else
	if (sscanf(line, "%d", &m) == 1)
	    return (m);
	fprintf(stdout, "No value or value out of range; please try again\n");
#endif
    }
    plexit("Too many tries.");
    return (0);
}

/*--------------------------------------------------------------------------*\
 * plGetFlt()
 *
 * Prompts human to input a float in response to given message.
\*--------------------------------------------------------------------------*/

PLFLT
plGetFlt(char *s)
{
    PLFLT m;
    double m1;
    int i = 0;
    char line[256];

    while (i++ < 10) {
	fprintf(stdout, s);
	fgets(line, sizeof(line), stdin);
#ifdef MSDOS
	m = atof(line);
	return (m);
#else
	if (sscanf(line, "%lf", &m1) == 1) {
	    m = (PLFLT) m1;
	    return (m);
	}
	fprintf(stdout, "No value or value out of range; please try again\n");
#endif
    }
    plexit("Too many tries.");
    return (0.);
}

/*--------------------------------------------------------------------------*\
 * plstrdup()
 *
 * A replacement for strdup(), which isn't portable.
 * Caller responsible for freeing the allocated memory.
\*--------------------------------------------------------------------------*/

char *
plstrdup(const char *src)
{
    char *dest = (char *) malloc( (strlen(src) + 1) * sizeof(char) );
    if (dest != NULL)
	strcpy(dest, src);
    else
	plabort("Out of memory");

    return dest;
}
 
