/* Copyright (C) 1995, 1996, 1997, 1998 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: gxpdash.c,v 1.2.6.1.2.1 2003/01/17 00:49:04 giles Exp $ */
/* Dash expansion for paths */
#include "math_.h"
#include "gx.h"
#include "gsmatrix.h"		/* for gscoord.h */
#include "gscoord.h"
#include "gxfixed.h"
#include "gsline.h"
#include "gzline.h"
#include "gzpath.h"

/* Expand a dashed path into explicit segments. */
/* The path contains no curves. */
private int subpath_expand_dashes(P4(const subpath *, gx_path *,
				     const gs_imager_state *,
				     const gx_dash_params *));
int
gx_path_add_dash_expansion(const gx_path * ppath_old, gx_path * ppath,
			   const gs_imager_state * pis)
{
    const subpath *psub;
    const gx_dash_params *dash = &gs_currentlineparams(pis)->dash;
    int code = 0;

    if (dash->pattern_size == 0)
	return gx_path_copy(ppath_old, ppath);
    for (psub = ppath_old->first_subpath; psub != 0 && code >= 0;
	 psub = (const subpath *)psub->last->next
	)
	code = subpath_expand_dashes(psub, ppath, pis, dash);
    return code;
}

private int
subpath_expand_dashes(const subpath * psub, gx_path * ppath,
		   const gs_imager_state * pis, const gx_dash_params * dash)
{
    const float *pattern = dash->pattern;
    int count, index;
    bool ink_on;
    double elt_length;
    fixed x0 = psub->pt.x, y0 = psub->pt.y;
    fixed x, y;
    const segment *pseg;
    int wrap = (dash->init_ink_on && psub->is_closed ? -1 : 0);
    int drawing = wrap;
    segment_notes notes = ~sn_not_first;
    int code;

    if ((code = gx_path_add_point(ppath, x0, y0)) < 0)
	return code;
    /*
     * To do the right thing at the beginning of a closed path, we have
     * to skip any initial line, and then redo it at the end of the
     * path.  Drawing = -1 while skipping, 0 while drawing normally, and
     * 1 on the second round.  Note that drawing != 0 implies ink_on.
     */
  top:count = dash->pattern_size;
    ink_on = dash->init_ink_on;
    index = dash->init_index;
    elt_length = dash->init_dist_left;
    x = x0, y = y0;
    pseg = (const segment *)psub;
    while ((pseg = pseg->next) != 0 && pseg->type != s_start) {
	fixed sx = pseg->pt.x, sy = pseg->pt.y;
	fixed udx = sx - x, udy = sy - y;
	double length, dx, dy;
	double scale = 1;
	double left;

	if (!(udx | udy))	/* degenerate */
	    dx = 0, dy = 0, length = 0;
	else {
	    gs_point d;

	    dx = udx, dy = udy;	/* scaled as fixed */
	    gs_imager_idtransform(pis, dx, dy, &d);
	    length = hypot(d.x, d.y) * (1.0 / fixed_1);
	    if (gs_imager_currentdashadapt(pis)) {
		double reps = length / dash->pattern_length;

		scale = reps / ceil(reps);
		/* Ensure we're starting at the start of a */
		/* repetition.  (This shouldn't be necessary, */
		/* but it is.) */
		count = dash->pattern_size;
		ink_on = dash->init_ink_on;
		index = dash->init_index;
		elt_length = dash->init_dist_left * scale;
	    }
	}
	left = length;
	while (left > elt_length) {	/* We are using up the line segment. */
	    double fraction = elt_length / length;
	    fixed nx = x + (fixed) (dx * fraction);
	    fixed ny = y + (fixed) (dy * fraction);

	    if (ink_on) {
		if (drawing >= 0)
		    code = gx_path_add_line_notes(ppath, nx, ny,
						  notes & pseg->notes);
		notes |= sn_not_first;
	    } else {
		if (drawing > 0)	/* done */
		    return 0;
		code = gx_path_add_point(ppath, nx, ny);
		notes &= ~sn_not_first;
		drawing = 0;
	    }
	    if (code < 0)
		return code;
	    left -= elt_length;
	    ink_on = !ink_on;
	    if (++index == count)
		index = 0;
	    elt_length = pattern[index] * scale;
	    x = nx, y = ny;
	}
	elt_length -= left;
	/* Handle the last dash of a segment. */
      on:if (ink_on) {
	    if (drawing >= 0) {
		code =
		    (pseg->type == s_line_close && drawing > 0 ?
		     gx_path_close_subpath_notes(ppath,
						 notes & pseg->notes) :
		     gx_path_add_line_notes(ppath, sx, sy,
					    notes & pseg->notes));
		notes |= sn_not_first;
	    }
	} else {
	    code = gx_path_add_point(ppath, sx, sy);
	    notes &= ~sn_not_first;
	    if (elt_length < fixed2float(fixed_epsilon) &&
		(pseg->next == 0 || pseg->next->type == s_start)
		) {		/*
				 * Ink is off, but we're within epsilon of the end
				 * of the dash element, and at the end of the
				 * subpath.  "Stretch" a little so we get a dot.
				 */
		if (code < 0)
		    return code;
		elt_length = 0;
		ink_on = true;
		if (++index == count)
		    index = 0;
		elt_length = pattern[index] * scale;
		goto on;
	    }
	    if (drawing > 0)	/* done */
		return code;
	    drawing = 0;
	}
	if (code < 0)
	    return code;
	x = sx, y = sy;
    }
    /* Check for wraparound. */
    if (wrap && drawing <= 0) {	/* We skipped some initial lines. */
	/* Go back and do them now. */
	drawing = 1;
	goto top;
    }
    return 0;
}
