/* Copyright (C) 1989, 1995, 1996, 1997, 1999 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gsline.c,v 1.2.6.1.2.1 2003/01/17 00:49:02 giles Exp $ */
/* Line parameter operators for Ghostscript library */
#include "math_.h"
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gxfixed.h"		/* ditto */
#include "gxmatrix.h"		/* for gzstate */
#include "gzstate.h"
#include "gscoord.h"		/* for currentmatrix, setmatrix */
#include "gsline.h"		/* for prototypes */
#include "gzline.h"

/* ------ Device-independent parameters ------ */

#define pgs_lp gs_currentlineparams_inline(pgs)

/* setlinewidth */
int
gs_setlinewidth(gs_state * pgs, floatp width)
{
    gx_set_line_width(pgs_lp, width);
    return 0;
}

/* currentlinewidth */
float
gs_currentlinewidth(const gs_state * pgs)
{
    return gx_current_line_width(pgs_lp);
}

/* setlinecap */
int
gs_setlinecap(gs_state * pgs, gs_line_cap cap)
{
    if ((uint) cap > gs_line_cap_max)
	return_error(gs_error_rangecheck);
    pgs_lp->cap = cap;
    return 0;
}

/* currentlinecap */
gs_line_cap
gs_currentlinecap(const gs_state * pgs)
{
    return pgs_lp->cap;
}

/* setlinejoin */
int
gs_setlinejoin(gs_state * pgs, gs_line_join join)
{
    if ((uint) join > gs_line_join_max)
	return_error(gs_error_rangecheck);
    pgs_lp->join = join;
    return 0;
}

/* currentlinejoin */
gs_line_join
gs_currentlinejoin(const gs_state * pgs)
{
    return pgs_lp->join;
}

/* setmiterlimit */
int
gx_set_miter_limit(gx_line_params * plp, floatp limit)
{
    if (limit < 1.0)
	return_error(gs_error_rangecheck);
    plp->miter_limit = limit;
    /*
     * Compute the miter check value.  The supplied miter limit is an
     * upper bound on 1/sin(phi/2); we convert this to a lower bound on
     * tan(phi).  Note that if phi > pi/2, this is negative.  We use the
     * half-angle and angle-sum formulas here to avoid the trig functions.
     * We also need a special check for phi/2 close to pi/4.
     * Some C compilers can't handle this as a conditional expression....
     */
    {
	double limit_squared = limit * limit;

	if (limit_squared < 2.0001 && limit_squared > 1.9999)
	    plp->miter_check = 1.0e6;
	else
	    plp->miter_check =
		sqrt(limit_squared - 1) * 2 / (limit_squared - 2);
    }
    return 0;
}
int
gs_setmiterlimit(gs_state * pgs, floatp limit)
{
    return gx_set_miter_limit(pgs_lp, limit);
}

/* currentmiterlimit */
float
gs_currentmiterlimit(const gs_state * pgs)
{
    return pgs_lp->miter_limit;
}

/* setdash */
int
gx_set_dash(gx_dash_params * dash, const float *pattern, uint length,
	    floatp offset, gs_memory_t * mem)
{
    uint n = length;
    const float *dfrom = pattern;
    bool ink = true;
    int index = 0;
    float pattern_length = 0.0;
    float dist_left;
    float *ppat = dash->pattern;

    /* Check the dash pattern. */
    while (n--) {
	float elt = *dfrom++;

	if (elt < 0)
	    return_error(gs_error_rangecheck);
	pattern_length += elt;
    }
    if (length == 0) {		/* empty pattern */
	dist_left = 0.0;
	if (mem && ppat) {
	    gs_free_object(mem, ppat, "gx_set_dash(old pattern)");
	    ppat = 0;
	}
    } else {
	uint size = length * sizeof(float);

	if (pattern_length == 0)
	    return_error(gs_error_rangecheck);
	/* Compute the initial index, ink_on, and distance left */
	/* in the pattern, according to the offset. */
#define f_mod(a, b) ((a) - floor((a) / (b)) * (b))
	if (length & 1) {	/* Odd and even repetitions of the pattern */
	    /* have opposite ink values! */
	    float length2 = pattern_length * 2;

	    dist_left = f_mod(offset, length2);
	    if (dist_left >= pattern_length)
		dist_left -= pattern_length, ink = !ink;
	} else
	    dist_left = f_mod(offset, pattern_length);
	while ((dist_left -= pattern[index]) >= 0 &&
	       (dist_left > 0 || pattern[index] != 0)
	    )
	    ink = !ink, index++;
	if (mem) {
	    if (ppat == 0)
		ppat = (float *)gs_alloc_bytes(mem, size,
					       "gx_set_dash(pattern)");
	    else if (length != dash->pattern_size)
		ppat = gs_resize_object(mem, ppat, size,
					"gx_set_dash(pattern)");
	    if (ppat == 0)
		return_error(gs_error_VMerror);
	}
	memcpy(ppat, pattern, length * sizeof(float));
    }
    dash->pattern = ppat;
    dash->pattern_size = length;
    dash->offset = offset;
    dash->pattern_length = pattern_length;
    dash->init_ink_on = ink;
    dash->init_index = index;
    dash->init_dist_left = -dist_left;
    return 0;
}
int
gs_setdash(gs_state * pgs, const float *pattern, uint length, floatp offset)
{
    return gx_set_dash(&pgs_lp->dash, pattern, length, offset,
		       pgs->memory);
}

/* currentdash */
uint
gs_currentdash_length(const gs_state * pgs)
{
    return pgs_lp->dash.pattern_size;
}
const float *
gs_currentdash_pattern(const gs_state * pgs)
{
    return pgs_lp->dash.pattern;
}
float
gs_currentdash_offset(const gs_state * pgs)
{
    return pgs_lp->dash.offset;
}

/* Internal accessor for line parameters */
const gx_line_params *
gs_currentlineparams(const gs_imager_state * pis)
{
    return gs_currentlineparams_inline(pis);
}

/* ------ Device-dependent parameters ------ */

/* setflat */
int
gs_imager_setflat(gs_imager_state * pis, floatp flat)
{
    if (flat <= 0.2)
	flat = 0.2;
    else if (flat > 100)
	flat = 100;
    pis->flatness = flat;
    return 0;
}
int
gs_setflat(gs_state * pgs, floatp flat)
{
    return gs_imager_setflat((gs_imager_state *) pgs, flat);
}

/* currentflat */
float
gs_currentflat(const gs_state * pgs)
{
    return pgs->flatness;
}

/* setstrokeadjust */
int
gs_setstrokeadjust(gs_state * pgs, bool stroke_adjust)
{
    pgs->stroke_adjust = stroke_adjust;
    return 0;
}

/* currentstrokeadjust */
bool
gs_currentstrokeadjust(const gs_state * pgs)
{
    return pgs->stroke_adjust;
}

/* ------ Extensions ------ */

/* Device-independent */

/* setdashadapt */
void
gs_setdashadapt(gs_state * pgs, bool adapt)
{
    pgs_lp->dash.adapt = adapt;
}

/* currentdashadapt */
bool
gs_imager_currentdashadapt(const gs_imager_state * pis)
{
    return gs_currentlineparams_inline(pis)->dash.adapt;
}
bool
gs_currentdashadapt(const gs_state * pgs)
{
    return gs_imager_currentdashadapt((const gs_imager_state *)pgs);
}

/* setcurvejoin */
int
gs_setcurvejoin(gs_state * pgs, int join)
{
    if (join < -1 || join > gs_line_join_max)
	return_error(gs_error_rangecheck);
    pgs_lp->curve_join = join;
    return 0;
}

/* currentcurvejoin */
int
gs_currentcurvejoin(const gs_state * pgs)
{
    return pgs_lp->curve_join;
}

/* Device-dependent */

/* setaccuratecurves */
void
gs_setaccuratecurves(gs_state * pgs, bool accurate)
{
    pgs->accurate_curves = accurate;
}

/* currentaccuratecurves */
bool
gs_imager_currentaccuratecurves(const gs_imager_state * pis)
{
    return pis->accurate_curves;
}
bool
gs_currentaccuratecurves(const gs_state * pgs)
{
    return gs_imager_currentaccuratecurves((const gs_imager_state *)pgs);
}

/* setdotlength */
int
gx_set_dot_length(gx_line_params * plp, floatp length, bool absolute)
{
    if (length < 0)
	return_error(gs_error_rangecheck);
    plp->dot_length = length;
    plp->dot_length_absolute = absolute;
    return 0;
}
int
gs_setdotlength(gs_state * pgs, floatp length, bool absolute)
{
    return gx_set_dot_length(pgs_lp, length, absolute);
}

/* currentdotlength */
float
gs_currentdotlength(const gs_state * pgs)
{
    return pgs_lp->dot_length;
}
bool
gs_currentdotlength_absolute(const gs_state * pgs)
{
    return pgs_lp->dot_length_absolute;
}

/* setdotorientation */
int
gs_setdotorientation(gs_state *pgs)
{
    if (is_xxyy(&pgs->ctm) || is_xyyx(&pgs->ctm))
	return gs_currentmatrix(pgs, &pgs_lp->dot_orientation);
    return_error(gs_error_rangecheck);
}

/* dotorientation */
int
gs_dotorientation(gs_state *pgs)
{
    return gs_setmatrix(pgs, &pgs_lp->dot_orientation);
}
