/* Copyright (C) 1997, 2000 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: gdevvec.c,v 1.12.2.2.2.1 2003/01/17 00:49:01 giles Exp $ */
/* Utilities for "vector" devices */
#include "math_.h"
#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gp.h"
#include "gserrors.h"
#include "gsparam.h"
#include "gsutil.h"
#include "gxfixed.h"
#include "gdevvec.h"
#include "gscspace.h"
#include "gxdcolor.h"
#include "gxpaint.h"		/* requires gx_path, ... */
#include "gzpath.h"
#include "gzcpath.h"

/* Structure descriptors */
public_st_device_vector();
public_st_vector_image_enum();

/* ================ Default implementations of vector procs ================ */

int
gdev_vector_setflat(gx_device_vector * vdev, floatp flatness)
{
    return 0;
}

/* Put a path on the output file. */
private bool
coord_between(fixed start, fixed mid, fixed end)
{
    return (start <= end ? start <= mid && mid <= end :
	    start >= mid && mid >= end);
}
int
gdev_vector_dopath(gx_device_vector *vdev, const gx_path * ppath,
		   gx_path_type_t type, const gs_matrix *pmat)
{
    bool do_close =
	(type & (gx_path_type_stroke | gx_path_type_always_close)) != 0;
    gs_fixed_rect rbox;
    gx_path_rectangular_type rtype = gx_path_is_rectangular(ppath, &rbox);
    gs_path_enum cenum;
    gdev_vector_dopath_state_t state;
    gs_fixed_point line_start, line_end;
    bool incomplete_line = false;
    bool need_moveto = false;
    int code;

    gdev_vector_dopath_init(&state, vdev, type, pmat);
    /*
     * if the path type is stroke, we only recognize closed
     * rectangles; otherwise, we recognize all rectangles.
     * Note that for stroking with a transformation, we can't use dorect,
     * which requires (untransformed) device coordinates.
     */
    if (rtype != prt_none &&
	!((type & gx_path_type_stroke) && rtype == prt_open) &&
	(pmat == 0 || is_xxyy(pmat) || is_xyyx(pmat)) &&
	(state.scale_mat.xx == 1.0 && state.scale_mat.yy == 1.0 &&
	 is_xxyy(&state.scale_mat) &&
	 is_fzero2(state.scale_mat.tx, state.scale_mat.ty))
	) {
	gs_point p, q;

	gs_point_transform_inverse((floatp)rbox.p.x, (floatp)rbox.p.y,
				   &state.scale_mat, &p);
	gs_point_transform_inverse((floatp)rbox.q.x, (floatp)rbox.q.y,
				   &state.scale_mat, &q);
	code = vdev_proc(vdev, dorect)(vdev, (fixed)p.x, (fixed)p.y,
				       (fixed)q.x, (fixed)q.y, type);
	if (code >= 0)
	    return code;
	/* If the dorect proc failed, use a general path. */
    }
    code = vdev_proc(vdev, beginpath)(vdev, type);
    if (code < 0)
	return code;
    gx_path_enum_init(&cenum, ppath);
    for (;;) {
	gs_fixed_point vs[3];
	int pe_op = gx_path_enum_next(&cenum, vs);

    sw:
	if (type & gx_path_type_optimize) {
	opt:
	    if (pe_op == gs_pe_lineto) {
		if (!incomplete_line) {
		    line_end = vs[0];
		    incomplete_line = true;
		    continue;
		}
		/*
		 * Merge collinear horizontal or vertical line segments
		 * going in the same direction.
		 */
		if (vs[0].x == line_end.x) {
		    if (vs[0].x == line_start.x &&
			coord_between(line_start.y, line_end.y, vs[0].y)
			) {
			line_end.y = vs[0].y;
			continue;
		    }
		} else if (vs[0].y == line_end.y) {
		    if (vs[0].y == line_start.y &&
			coord_between(line_start.x, line_end.x, vs[0].x)
			) {
			line_end.x = vs[0].x;
			continue;
		    }
		}
	    }
	    if (incomplete_line) {
		if (need_moveto) {	/* see gs_pe_moveto case */
		    code = gdev_vector_dopath_segment(&state, gs_pe_moveto,
						      &line_start);
		    if (code < 0)
			return code;
		    need_moveto = false;
		}
		code = gdev_vector_dopath_segment(&state, gs_pe_lineto,
						  &line_end);
		if (code < 0)
		    return code;
		line_start = line_end;
		incomplete_line = false;
		goto opt;
	    }
	}
	switch (pe_op) {
	case 0:		/* done */
	done:
	    code = vdev_proc(vdev, endpath)(vdev, type);
	    return (code < 0 ? code : 0);
	case gs_pe_curveto:
	    if (need_moveto) {	/* see gs_pe_moveto case */
		code = gdev_vector_dopath_segment(&state, gs_pe_moveto,
						  &line_start);
		if (code < 0)
		    return code;
		need_moveto = false;
	    }
	    line_start = vs[2];
	    goto draw;
	case gs_pe_moveto:
	    /*
	     * A bug in Acrobat Reader 4 causes it to draw a single pixel
	     * for a fill with an isolated moveto.  If we're doing a fill
	     * without a stroke, defer emitting a moveto until we know that
	     * the subpath has more elements.
	     */
	    line_start = vs[0];
	    if (!(type & gx_path_type_stroke) && (type & gx_path_type_fill)) {
		need_moveto = true;
		continue;
	    }
	    goto draw;
	case gs_pe_lineto:
	    if (need_moveto) {	/* see gs_pe_moveto case */
		code = gdev_vector_dopath_segment(&state, gs_pe_moveto,
						  &line_start);
		if (code < 0)
		    return code;
		need_moveto = false;
	    }
	    line_start = vs[0];
	    goto draw;
	case gs_pe_closepath:
	    if (need_moveto) {	/* see gs_pe_moveto case */
		need_moveto = false;
		continue;
	    }
	    if (!do_close) {
		pe_op = gx_path_enum_next(&cenum, vs);
		if (pe_op == 0)
		    goto done;
		code = gdev_vector_dopath_segment(&state, gs_pe_closepath, vs);
		if (code < 0)
		    return code;
		goto sw;
	    }
	    /* falls through */
	draw:
	    code = gdev_vector_dopath_segment(&state, pe_op, vs);
	    if (code < 0)
		return code;
	}
	incomplete_line = false; /* only needed if optimizing */
    }
}

int
gdev_vector_dorect(gx_device_vector * vdev, fixed x0, fixed y0, fixed x1,
		   fixed y1, gx_path_type_t type)
{
    int code = (*vdev_proc(vdev, beginpath)) (vdev, type);

    if (code < 0)
	return code;
    code = gdev_vector_write_rectangle(vdev, x0, y0, x1, y1,
				       (type & gx_path_type_stroke) != 0,
				       gx_rect_x_first);
    if (code < 0)
	return code;
    return (*vdev_proc(vdev, endpath)) (vdev, type);
}

/* ================ Utility procedures ================ */

/* Recompute the cached color values. */
private void
gdev_vector_load_cache(gx_device_vector * vdev)
{
    vdev->black = gx_device_black((gx_device *)vdev);
    vdev->white = gx_device_white((gx_device *)vdev);
}

/* Initialize the state. */
void
gdev_vector_init(gx_device_vector * vdev)
{
    gdev_vector_reset(vdev);
    vdev->scale.x = vdev->scale.y = 1.0;
    vdev->in_page = false;
    gdev_vector_load_cache(vdev);
}

/* Reset the remembered graphics state. */
void
gdev_vector_reset(gx_device_vector * vdev)
{
    static const gs_imager_state state_initial =
    {gs_imager_state_initial(1)};

    vdev->state = state_initial;
    color_unset(&vdev->fill_color);
    color_unset(&vdev->stroke_color);
    vdev->clip_path_id =
	vdev->no_clip_path_id = gs_next_ids(1);
}

/* Open the output file and stream. */
int
gdev_vector_open_file_options(gx_device_vector * vdev, uint strmbuf_size,
			      int open_options)
{
    bool binary = !(open_options & VECTOR_OPEN_FILE_ASCII);
    int code = -1;		/* (only for testing, never returned) */

    /* Open the file as seekable or sequential, as requested. */
    if (!(open_options & VECTOR_OPEN_FILE_SEQUENTIAL)) {
	/* Try to open as seekable. */
	code =
	    gx_device_open_output_file((gx_device *)vdev, vdev->fname,
				       binary, true, &vdev->file);
    }
    if (code < 0 && (open_options & (VECTOR_OPEN_FILE_SEQUENTIAL |
				     VECTOR_OPEN_FILE_SEQUENTIAL_OK))) {
	/* Try to open as sequential. */
	code = gx_device_open_output_file((gx_device *)vdev, vdev->fname,
					  binary, false, &vdev->file);
    }
    if (code < 0)
	return code;
    if ((vdev->strmbuf = gs_alloc_bytes(vdev->v_memory, strmbuf_size,
					"vector_open(strmbuf)")) == 0 ||
	(vdev->strm = s_alloc(vdev->v_memory,
			      "vector_open(strm)")) == 0 ||
	((open_options & VECTOR_OPEN_FILE_BBOX) &&
	 (vdev->bbox_device =
	  gs_alloc_struct_immovable(vdev->v_memory,
				    gx_device_bbox, &st_device_bbox,
				    "vector_open(bbox_device)")) == 0)
	) {
	if (vdev->bbox_device)
	    gs_free_object(vdev->v_memory, vdev->bbox_device,
			   "vector_open(bbox_device)");
	vdev->bbox_device = 0;
	if (vdev->strm)
	    gs_free_object(vdev->v_memory, vdev->strm,
			   "vector_open(strm)");
	vdev->strm = 0;
	if (vdev->strmbuf)
	    gs_free_object(vdev->v_memory, vdev->strmbuf,
			   "vector_open(strmbuf)");
	vdev->strmbuf = 0;
	fclose(vdev->file);
	vdev->file = 0;
	return_error(gs_error_VMerror);
    }
    vdev->strmbuf_size = strmbuf_size;
    swrite_file(vdev->strm, vdev->file, vdev->strmbuf, strmbuf_size);
    vdev->open_options = open_options;
    /*
     * We don't want finalization to close the file, but we do want it
     * to flush the stream buffer.
     */
    vdev->strm->procs.close = vdev->strm->procs.flush;
    if (vdev->bbox_device) {
	gx_device_bbox_init(vdev->bbox_device, NULL);
	gx_device_set_resolution((gx_device *) vdev->bbox_device,
				 vdev->HWResolution[0],
				 vdev->HWResolution[1]);
	/* Do the right thing about upright vs. inverted. */
	/* (This is dangerous in general, since the procedure */
	/* might reference non-standard elements.) */
	set_dev_proc(vdev->bbox_device, get_initial_matrix,
		     dev_proc(vdev, get_initial_matrix));
	(*dev_proc(vdev->bbox_device, open_device))
	    ((gx_device *) vdev->bbox_device);
    }
    return 0;
}

/* Get the current stream, calling beginpage if in_page is false. */
stream *
gdev_vector_stream(gx_device_vector * vdev)
{
    if (!vdev->in_page) {
	(*vdev_proc(vdev, beginpage)) (vdev);
	vdev->in_page = true;
    }
    return vdev->strm;
}

/* Compare two drawing colors. */
/* Right now we don't attempt to handle non-pure colors. */
private bool
drawing_color_eq(const gx_drawing_color * pdc1, const gx_drawing_color * pdc2)
{
    return (gx_dc_is_pure(pdc1) ?
	    gx_dc_is_pure(pdc2) &&
	    gx_dc_pure_color(pdc1) == gx_dc_pure_color(pdc2) :
	    gx_dc_is_null(pdc1) ?
	    gx_dc_is_null(pdc2) :
	    false);
}

/* Update the logical operation. */
int
gdev_vector_update_log_op(gx_device_vector * vdev, gs_logical_operation_t lop)
{
    gs_logical_operation_t diff = lop ^ vdev->state.log_op;

    if (diff != 0) {
	int code = (*vdev_proc(vdev, setlogop)) (vdev, lop, diff);

	if (code < 0)
	    return code;
	vdev->state.log_op = lop;
    }
    return 0;
}

/* Update the fill color. */
int
gdev_vector_update_fill_color(gx_device_vector * vdev,
			      const gx_drawing_color * pdcolor)
{
    if (!drawing_color_eq(pdcolor, &vdev->fill_color)) {
	int code = (*vdev_proc(vdev, setfillcolor)) (vdev, pdcolor);

	if (code < 0)
	    return code;
	vdev->fill_color = *pdcolor;
    }
    return 0;
}

/* Update the state for filling a region. */
private int
update_fill(gx_device_vector * vdev, const gx_drawing_color * pdcolor,
	    gs_logical_operation_t lop)
{
    int code = gdev_vector_update_fill_color(vdev, pdcolor);

    if (code < 0)
	return code;
    return gdev_vector_update_log_op(vdev, lop);
}

/* Bring state up to date for filling. */
int
gdev_vector_prepare_fill(gx_device_vector * vdev, const gs_imager_state * pis,
	    const gx_fill_params * params, const gx_drawing_color * pdcolor)
{
    if (params->flatness != vdev->state.flatness) {
	int code = (*vdev_proc(vdev, setflat)) (vdev, params->flatness);

	if (code < 0)
	    return code;
	vdev->state.flatness = params->flatness;
    }
    return update_fill(vdev, pdcolor, pis->log_op);
}

/* Compare two dash patterns. */
private bool
dash_pattern_eq(const float *stored, const gx_dash_params * set, floatp scale)
{
    int i;

    for (i = 0; i < set->pattern_size; ++i)
	if (stored[i] != (float)(set->pattern[i] * scale))
	    return false;
    return true;
}

/* Bring state up to date for stroking. */
int
gdev_vector_prepare_stroke(gx_device_vector * vdev,
			   const gs_imager_state * pis,	/* may be NULL */
			   const gx_stroke_params * params, /* may be NULL */
			   const gx_drawing_color * pdcolor, /* may be NULL */
			   floatp scale)
{
    if (pis) {
	int pattern_size = pis->line_params.dash.pattern_size;
	float dash_offset = pis->line_params.dash.offset * scale;
	float half_width = pis->line_params.half_width * scale;

	if (pattern_size > max_dash)
	    return_error(gs_error_limitcheck);
	if (dash_offset != vdev->state.line_params.dash.offset ||
	    pattern_size != vdev->state.line_params.dash.pattern_size ||
	    (pattern_size != 0 &&
	     !dash_pattern_eq(vdev->dash_pattern, &pis->line_params.dash,
			      scale))
	    ) {
	    float pattern[max_dash];
	    int i, code;

	    for (i = 0; i < pattern_size; ++i)
		pattern[i] = pis->line_params.dash.pattern[i] * scale;
	    code = (*vdev_proc(vdev, setdash))
		(vdev, pattern, pattern_size, dash_offset);
	    if (code < 0)
		return code;
	    memcpy(vdev->dash_pattern, pattern, pattern_size * sizeof(float));

	    vdev->state.line_params.dash.pattern_size = pattern_size;
	    vdev->state.line_params.dash.offset = dash_offset;
	}
	if (half_width != vdev->state.line_params.half_width) {
	    int code = (*vdev_proc(vdev, setlinewidth))
		(vdev, half_width * 2);

	    if (code < 0)
		return code;
	    vdev->state.line_params.half_width = half_width;
	}
	if (pis->line_params.miter_limit != vdev->state.line_params.miter_limit) {
	    int code = (*vdev_proc(vdev, setmiterlimit))
		(vdev, pis->line_params.miter_limit);

	    if (code < 0)
		return code;
	    gx_set_miter_limit(&vdev->state.line_params,
			       pis->line_params.miter_limit);
	}
	if (pis->line_params.cap != vdev->state.line_params.cap) {
	    int code = (*vdev_proc(vdev, setlinecap))
		(vdev, pis->line_params.cap);

	    if (code < 0)
		return code;
	    vdev->state.line_params.cap = pis->line_params.cap;
	}
	if (pis->line_params.join != vdev->state.line_params.join) {
	    int code = (*vdev_proc(vdev, setlinejoin))
		(vdev, pis->line_params.join);

	    if (code < 0)
		return code;
	    vdev->state.line_params.join = pis->line_params.join;
	} {
	    int code = gdev_vector_update_log_op(vdev, pis->log_op);

	    if (code < 0)
		return code;
	}
    }
    if (params) {
	if (params->flatness != vdev->state.flatness) {
	    int code = (*vdev_proc(vdev, setflat)) (vdev, params->flatness);

	    if (code < 0)
		return code;
	    vdev->state.flatness = params->flatness;
	}
    }
    if (pdcolor) {
	if (!drawing_color_eq(pdcolor, &vdev->stroke_color)) {
	    int code = (*vdev_proc(vdev, setstrokecolor)) (vdev, pdcolor);

	    if (code < 0)
		return code;
	    vdev->stroke_color = *pdcolor;
	}
    }
    return 0;
}

/*
 * Compute the scale for transforming the line width and dash pattern for a
 * stroke operation, and, if necessary to handle anisotropic scaling, a full
 * transformation matrix to be inverse-applied to the path elements as well.
 * Return 0 if only scaling, 1 if a full matrix is needed.
 */
int
gdev_vector_stroke_scaling(const gx_device_vector *vdev,
			   const gs_imager_state *pis,
			   double *pscale, gs_matrix *pmat)
{
    bool set_ctm = true;
    double scale = 1;

    /*
     * If the CTM is not uniform, stroke width depends on angle.
     * We'd like to avoid resetting the CTM, so we check for uniform
     * CTMs explicitly.  Note that in PDF, unlike PostScript, it is
     * the CTM at the time of the stroke operation, not the CTM at
     * the time the path was constructed, that is used for transforming
     * the points of the path; so if we have to reset the CTM, we must
     * do it before constructing the path, and inverse-transform all
     * the coordinates.
     */
    if (is_xxyy(&pis->ctm)) {
	scale = fabs(pis->ctm.xx);
	set_ctm = fabs(pis->ctm.yy) != scale;
    } else if (is_xyyx(&pis->ctm)) {
	scale = fabs(pis->ctm.xy);
	set_ctm = fabs(pis->ctm.yx) != scale;
    } else if ((pis->ctm.xx == pis->ctm.yy && pis->ctm.xy == -pis->ctm.yx) ||
	       (pis->ctm.xx == -pis->ctm.yy && pis->ctm.xy == pis->ctm.yx)
	) {
	scale = hypot(pis->ctm.xx, pis->ctm.xy);
	set_ctm = false;
    }
    if (set_ctm) {
	/*
	 * Adobe Acrobat Reader has limitations on the maximum user
	 * coordinate value.  If we scale the matrix down too far, the
	 * coordinates will get too big: limit the scale factor to prevent
	 * this from happening.  (This does no harm for other output
	 * formats.)
	 */
	double
	    mxx = pis->ctm.xx / vdev->scale.x,
	    mxy = pis->ctm.xy / vdev->scale.y,
	    myx = pis->ctm.yx / vdev->scale.x,
	    myy = pis->ctm.yy / vdev->scale.y;

	scale = 0.5 * (fabs(mxx) + fabs(mxy) + fabs(myx) + fabs(myy));
	pmat->xx = mxx / scale, pmat->xy = mxy / scale;
	pmat->yx = myx / scale, pmat->yy = myy / scale;
	pmat->tx = pmat->ty = 0;
    }
    *pscale = scale;
    return (int)set_ctm;
}

/* Initialize for writing a path using the default implementation. */
void
gdev_vector_dopath_init(gdev_vector_dopath_state_t *state,
			gx_device_vector *vdev, gx_path_type_t type,
			const gs_matrix *pmat)
{
    state->vdev = vdev;
    state->type = type;
    if (pmat) {
	state->scale_mat = *pmat;
	/*
	 * The path element writing procedures all divide the coordinates
	 * by the scale, so we must compensate for that here.
	 */
	gs_matrix_scale(&state->scale_mat, 1.0 / vdev->scale.x,
			1.0 / vdev->scale.y, &state->scale_mat);
    } else {
	gs_make_scaling(vdev->scale.x, vdev->scale.y, &state->scale_mat);
    }
    state->first = true;
}

/*
 * Put a segment of an enumerated path on the output file.
 * pe_op is assumed to be valid and non-zero.
 */
int
gdev_vector_dopath_segment(gdev_vector_dopath_state_t *state, int pe_op,
			   gs_fixed_point vs[3])
{
    gx_device_vector *vdev = state->vdev;
    const gs_matrix *const pmat = &state->scale_mat;
    gs_point vp[3];
    int code;

    switch (pe_op) {
	case gs_pe_moveto:
	    gs_point_transform_inverse(fixed2float(vs[0].x),
				       fixed2float(vs[0].y), pmat, &vp[0]);
	    if (state->first)
		state->start = vp[0], state->first = false;
	    code = vdev_proc(vdev, moveto)
		(vdev, state->prev.x, state->prev.y, vp[0].x, vp[0].y,
		 state->type);
	    state->prev = vp[0];
	    break;
	case gs_pe_lineto:
	    gs_point_transform_inverse(fixed2float(vs[0].x),
				       fixed2float(vs[0].y), pmat, &vp[0]);
	    code = vdev_proc(vdev, lineto)
		(vdev, state->prev.x, state->prev.y, vp[0].x, vp[0].y,
		 state->type);
	    state->prev = vp[0];
	    break;
	case gs_pe_curveto:
	    gs_point_transform_inverse(fixed2float(vs[0].x),
				       fixed2float(vs[0].y), pmat, &vp[0]);
	    gs_point_transform_inverse(fixed2float(vs[1].x),
				       fixed2float(vs[1].y), pmat, &vp[1]);
	    gs_point_transform_inverse(fixed2float(vs[2].x),
				       fixed2float(vs[2].y), pmat, &vp[2]);
	    code = vdev_proc(vdev, curveto)
		(vdev, state->prev.x, state->prev.y, vp[0].x, vp[0].y,
		 vp[1].x, vp[1].y, vp[2].x, vp[2].y, state->type);
	    state->prev = vp[2];
	    break;
	case gs_pe_closepath:
	    code = vdev_proc(vdev, closepath)
		(vdev, state->prev.x, state->prev.y, state->start.x,
		 state->start.y, state->type);
	    state->prev = state->start;
	    break;
	default:		/* can't happen */
	    return -1;
    }
    return code;
}

/* Write a polygon as part of a path. */
/* May call beginpath, moveto, lineto, closepath, endpath. */
int
gdev_vector_write_polygon(gx_device_vector * vdev, const gs_fixed_point * points,
			  uint count, bool close, gx_path_type_t type)
{
    int code = 0;

    if (type != gx_path_type_none &&
	(code = (*vdev_proc(vdev, beginpath)) (vdev, type)) < 0
	)
	return code;
    if (count > 0) {
	double x = fixed2float(points[0].x) / vdev->scale.x, y = fixed2float(points[0].y) / vdev->scale.y;
	double x_start = x, y_start = y, x_prev, y_prev;
	uint i;

	code = (*vdev_proc(vdev, moveto))
	    (vdev, 0.0, 0.0, x, y, type);
	if (code >= 0)
	    for (i = 1; i < count && code >= 0; ++i) {
		x_prev = x, y_prev = y;
		code = (*vdev_proc(vdev, lineto))
		    (vdev, x_prev, y_prev,
		     (x = fixed2float(points[i].x) / vdev->scale.x),
		     (y = fixed2float(points[i].y) / vdev->scale.y),
		     type);
	    }
	if (code >= 0 && close)
	    code = (*vdev_proc(vdev, closepath))
		(vdev, x, y, x_start, y_start, type);
    }
    return (code >= 0 && type != gx_path_type_none ?
	    (*vdev_proc(vdev, endpath)) (vdev, type) : code);
}

/* Write a rectangle as part of a path. */
/* May call moveto, lineto, closepath. */
int
gdev_vector_write_rectangle(gx_device_vector * vdev, fixed x0, fixed y0,
	      fixed x1, fixed y1, bool close, gx_rect_direction_t direction)
{
    gs_fixed_point points[4];

    points[0].x = x0, points[0].y = y0;
    points[2].x = x1, points[2].y = y1;
    if (direction == gx_rect_x_first)
	points[1].x = x1, points[1].y = y0,
	    points[3].x = x0, points[3].y = y1;
    else
	points[1].x = x0, points[1].y = y1,
	    points[3].x = x1, points[3].y = y0;
    return gdev_vector_write_polygon(vdev, points, 4, close,
				     gx_path_type_none);
}

/* Write a clipping path by calling the path procedures. */
int
gdev_vector_write_clip_path(gx_device_vector * vdev,
			    const gx_clip_path * pcpath)
{
    const gx_clip_rect *prect;
    gx_clip_rect page_rect;
    int code;

    if (pcpath == 0) {
	/* There's no special provision for initclip. */
	/* Write a rectangle that covers the entire page. */
	page_rect.xmin = page_rect.ymin = 0;
	page_rect.xmax = vdev->width;
	page_rect.ymax = vdev->height;
	page_rect.next = 0;
	prect = &page_rect;
    } else if (pcpath->path_valid) {
	return (*vdev_proc(vdev, dopath))
	    (vdev, &pcpath->path,
	     (pcpath->rule <= 0 ?
	      gx_path_type_clip | gx_path_type_winding_number :
	      gx_path_type_clip | gx_path_type_even_odd),
	     NULL);
    } else {
	const gx_clip_list *list = gx_cpath_list(pcpath);

	prect = list->head;
	if (prect == 0)
	    prect = &list->single;
    }
    /* Write out the rectangles. */
    code = (*vdev_proc(vdev, beginpath)) (vdev, gx_path_type_clip);
    for (; code >= 0 && prect != 0; prect = prect->next)
	if (prect->xmax > prect->xmin && prect->ymax > prect->ymin)
	    code = gdev_vector_write_rectangle
		(vdev, int2fixed(prect->xmin), int2fixed(prect->ymin),
		 int2fixed(prect->xmax), int2fixed(prect->ymax),
		 false, gx_rect_x_first);
    if (code >= 0)
	code = (*vdev_proc(vdev, endpath)) (vdev, gx_path_type_clip);
    return code;
}

/* Update the clipping path if needed. */
int
gdev_vector_update_clip_path(gx_device_vector * vdev,
			     const gx_clip_path * pcpath)
{
    if (pcpath) {
	if (pcpath->id != vdev->clip_path_id) {
	    int code = gdev_vector_write_clip_path(vdev, pcpath);

	    if (code < 0)
		return code;
	    vdev->clip_path_id = pcpath->id;
	}
    } else {
	if (vdev->clip_path_id != vdev->no_clip_path_id) {
	    int code = gdev_vector_write_clip_path(vdev, NULL);

	    if (code < 0)
		return code;
	    vdev->clip_path_id = vdev->no_clip_path_id;
	}
    }
    return 0;
}

/* Close the output file and stream. */
int
gdev_vector_close_file(gx_device_vector * vdev)
{
    FILE *f = vdev->file;
    int err;

    gs_free_object(vdev->v_memory, vdev->bbox_device,
		   "vector_close(bbox_device)");
    vdev->bbox_device = 0;
    sclose(vdev->strm);
    gs_free_object(vdev->v_memory, vdev->strm, "vector_close(strm)");
    vdev->strm = 0;
    gs_free_object(vdev->v_memory, vdev->strmbuf, "vector_close(strmbuf)");
    vdev->strmbuf = 0;
    vdev->file = 0;
    err = ferror(f);
    /* We prevented sclose from closing the file. */
    if (fclose(f) != 0 || err != 0)
	return_error(gs_error_ioerror);
    return 0;
}

/* ---------------- Image enumeration ---------------- */

/* Initialize for enumerating an image. */
int
gdev_vector_begin_image(gx_device_vector * vdev,
			const gs_imager_state * pis, const gs_image_t * pim,
			gs_image_format_t format, const gs_int_rect * prect,
	      const gx_drawing_color * pdcolor, const gx_clip_path * pcpath,
		    gs_memory_t * mem, const gx_image_enum_procs_t * pprocs,
			gdev_vector_image_enum_t * pie)
{
    const gs_color_space *pcs = pim->ColorSpace;
    int num_components;
    int bits_per_pixel;
    int code;

    if (pim->ImageMask)
	bits_per_pixel = num_components = 1;
    else
	num_components = gs_color_space_num_components(pcs),
	    bits_per_pixel = pim->BitsPerComponent;
    code = gx_image_enum_common_init((gx_image_enum_common_t *) pie,
				     (const gs_data_image_t *)pim,
				     pprocs, (gx_device *) vdev,
				     num_components, format);
    if (code < 0)
	return code;
    pie->bits_per_pixel = bits_per_pixel * num_components /
	pie->num_planes;
    pie->default_info = 0;
    pie->bbox_info = 0;
    if ((code = gdev_vector_update_log_op(vdev, pis->log_op)) < 0 ||
	(code = gdev_vector_update_clip_path(vdev, pcpath)) < 0 ||
	((pim->ImageMask ||
	  (pim->CombineWithColor && rop3_uses_T(pis->log_op))) &&
	 (code = gdev_vector_update_fill_color(vdev, pdcolor)) < 0) ||
	(vdev->bbox_device &&
	 (code = (*dev_proc(vdev->bbox_device, begin_image))
	  ((gx_device *) vdev->bbox_device, pis, pim, format, prect,
	   pdcolor, pcpath, mem, &pie->bbox_info)) < 0)
	)
	return code;
    pie->memory = mem;
    if (prect)
	pie->width = prect->q.x - prect->p.x,
	    pie->height = prect->q.y - prect->p.y;
    else
	pie->width = pim->Width, pie->height = pim->Height;
    pie->bits_per_row = pie->width * pie->bits_per_pixel;
    pie->y = 0;
    return 0;
}

/* End an image, optionally supplying any necessary blank padding rows. */
/* Return 0 if we used the default implementation, 1 if not. */
int
gdev_vector_end_image(gx_device_vector * vdev,
	 gdev_vector_image_enum_t * pie, bool draw_last, gx_color_index pad)
{
    int code;

    if (pie->default_info) {
	code = gx_default_end_image((gx_device *) vdev, pie->default_info,
				    draw_last);
	if (code >= 0)
	    code = 0;
    } else {			/* Fill out to the full image height. */
	if (pie->y < pie->height && pad != gx_no_color_index) {
	    uint bytes_per_row = (pie->bits_per_row + 7) >> 3;
	    byte *row = gs_alloc_bytes(pie->memory, bytes_per_row,
				       "gdev_vector_end_image(fill)");

	    if (row == 0)
		return_error(gs_error_VMerror);
/****** FILL VALUE IS WRONG ******/
	    memset(row, (byte) pad, bytes_per_row);
	    for (; pie->y < pie->height; pie->y++)
		gx_image_data((gx_image_enum_common_t *) pie,
			      (const byte **)&row, 0,
			      bytes_per_row, 1);
	    gs_free_object(pie->memory, row,
			   "gdev_vector_end_image(fill)");
	}
	code = 1;
    }
    if (vdev->bbox_device) {
	int bcode = gx_image_end(pie->bbox_info, draw_last);

	if (bcode < 0)
	    code = bcode;
    }
    gs_free_object(pie->memory, pie, "gdev_vector_end_image");
    return code;
}

/* ================ Device procedures ================ */

#define vdev ((gx_device_vector *)dev)

/* Get parameters. */
int
gdev_vector_get_params(gx_device * dev, gs_param_list * plist)
{
    int code = gx_default_get_params(dev, plist);
    int ecode;
    gs_param_string ofns;

    if (code < 0)
	return code;
    ofns.data = (const byte *)vdev->fname,
	ofns.size = strlen(vdev->fname),
	ofns.persistent = false;
    if ((ecode = param_write_string(plist, "OutputFile", &ofns)) < 0)
	return ecode;
    return code;
}

/* Put parameters. */
int
gdev_vector_put_params(gx_device * dev, gs_param_list * plist)
{
    int ecode = 0;
    int code;
    gs_param_name param_name;
    gs_param_string ofns;

    switch (code = param_read_string(plist, (param_name = "OutputFile"), &ofns)) {
	case 0:
	    /*
	     * Vector devices typically write header information at the
	     * beginning of the file: changing the file name after writing
	     * any pages should be an error.
	     */
	    if (ofns.size > fname_size)
		ecode = gs_error_limitcheck;
	    else if (!bytes_compare(ofns.data, ofns.size,
				    (const byte *)vdev->fname,
				    strlen(vdev->fname))
		     ) {
		/* The new name is the same as the old name.  Do nothing. */
		ofns.data = 0;
		break;
	    } else if (dev->LockSafetyParams ||
	    		(dev->is_open && vdev->strm != 0 &&
		       stell(vdev->strm) != 0)
		       )
		ecode = (dev->LockSafetyParams) ? gs_error_invalidaccess : 
				gs_error_rangecheck;
	    else
		break;
	    goto ofe;
	default:
	    ecode = code;
	  ofe:param_signal_error(plist, param_name, ecode);
	case 1:
	    ofns.data = 0;
	    break;
    }

    if (ecode < 0)
	return ecode;
    {
	bool open = dev->is_open;

	/* Don't let gx_default_put_params close the device. */
	dev->is_open = false;
	code = gx_default_put_params(dev, plist);
	dev->is_open = open;
    }
    if (code < 0)
	return code;

    if (ofns.data != 0) {
	memcpy(vdev->fname, ofns.data, ofns.size);
	vdev->fname[ofns.size] = 0;
	if (vdev->file != 0) {
	    gx_device_bbox *bbdev = vdev->bbox_device;

	    vdev->bbox_device = 0; /* don't let it be freed */
	    code = gdev_vector_close_file(vdev);
	    vdev->bbox_device = bbdev;
	    if (code < 0)
		return code;
	    return gdev_vector_open_file_options(vdev, vdev->strmbuf_size,
						 vdev->open_options);
	}
    }
    gdev_vector_load_cache(vdev);	/* in case color mapping changed */
    return 0;
}

/* ---------------- Defaults ---------------- */

int
gdev_vector_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
			   gx_color_index color)
{
    gx_drawing_color dcolor;

    /* Ignore the initial fill with white. */
    if (!vdev->in_page && color == vdev->white)
	return 0;
    color_set_pure(&dcolor, color);
    {
	int code = update_fill(vdev, &dcolor, rop3_T);

	if (code < 0)
	    return code;
	/* Make sure we aren't being clipped. */
	code = gdev_vector_update_clip_path(vdev, NULL);
	if (code < 0)
	    return code;
    }
    if (vdev->bbox_device) {
	int code = (*dev_proc(vdev->bbox_device, fill_rectangle))
	((gx_device *) vdev->bbox_device, x, y, w, h, color);

	if (code < 0)
	    return code;
    }
    return (*vdev_proc(vdev, dorect)) (vdev, int2fixed(x), int2fixed(y),
				       int2fixed(x + w), int2fixed(y + h),
				       gx_path_type_fill);
}

int
gdev_vector_fill_path(gx_device * dev, const gs_imager_state * pis,
		      gx_path * ppath, const gx_fill_params * params,
		 const gx_device_color * pdevc, const gx_clip_path * pcpath)
{
    int code;

    if ((code = gdev_vector_prepare_fill(vdev, pis, params, pdevc)) < 0 ||
	(code = gdev_vector_update_clip_path(vdev, pcpath)) < 0 ||
	(vdev->bbox_device &&
	 (code = (*dev_proc(vdev->bbox_device, fill_path))
	  ((gx_device *) vdev->bbox_device, pis, ppath, params,
	   pdevc, pcpath)) < 0) ||
	(code = (*vdev_proc(vdev, dopath))
	 (vdev, ppath,
	  (params->rule > 0 ? gx_path_type_even_odd :
	   gx_path_type_winding_number) | gx_path_type_fill |
	   vdev->fill_options,
	 NULL)) < 0
	)
	return gx_default_fill_path(dev, pis, ppath, params, pdevc, pcpath);
    return code;
}

int
gdev_vector_stroke_path(gx_device * dev, const gs_imager_state * pis,
			gx_path * ppath, const gx_stroke_params * params,
	      const gx_drawing_color * pdcolor, const gx_clip_path * pcpath)
{
    int code;
    double scale;
    int set_ctm;
    gs_matrix mat;

    if ((set_ctm = gdev_vector_stroke_scaling(vdev, pis, &scale, &mat)) != 0 ||
	(code = gdev_vector_prepare_stroke(vdev, pis, params, pdcolor, scale)) < 0 ||
	(code = gdev_vector_update_clip_path(vdev, pcpath)) < 0 ||
	(vdev->bbox_device &&
	 (code = (*dev_proc(vdev->bbox_device, stroke_path))
	  ((gx_device *) vdev->bbox_device, pis, ppath, params,
	   pdcolor, pcpath)) < 0) ||
	(code = (*vdev_proc(vdev, dopath))
	 (vdev, ppath, gx_path_type_stroke | vdev->stroke_options, NULL)) < 0
	)
	return gx_default_stroke_path(dev, pis, ppath, params, pdcolor, pcpath);
    return code;
}

int
gdev_vector_fill_trapezoid(gx_device * dev, const gs_fixed_edge * left,
	const gs_fixed_edge * right, fixed ybot, fixed ytop, bool swap_axes,
		  const gx_device_color * pdevc, gs_logical_operation_t lop)
{
    fixed xl = left->start.x;
    fixed wl = left->end.x - xl;
    fixed yl = left->start.y;
    fixed hl = left->end.y - yl;
    fixed xr = right->start.x;
    fixed wr = right->end.x - xr;
    fixed yr = right->start.y;
    fixed hr = right->end.y - yr;
    fixed x0l = xl + fixed_mult_quo(wl, ybot - yl, hl);
    fixed x1l = xl + fixed_mult_quo(wl, ytop - yl, hl);
    fixed x0r = xr + fixed_mult_quo(wr, ybot - yr, hr);
    fixed x1r = xr + fixed_mult_quo(wr, ytop - yr, hr);

#define y0 ybot
#define y1 ytop
    int code = update_fill(vdev, pdevc, lop);
    gs_fixed_point points[4];

    if (code < 0)
	return gx_default_fill_trapezoid(dev, left, right, ybot, ytop,
					 swap_axes, pdevc, lop);
    /* Make sure we aren't being clipped. */
    code = gdev_vector_update_clip_path(vdev, NULL);
    if (code < 0)
	return code;
    if (swap_axes)
	points[0].y = x0l, points[1].y = x0r,
	    points[0].x = points[1].x = y0,
	    points[2].y = x1r, points[3].y = x1l,
	    points[2].x = points[3].x = y1;
    else
	points[0].x = x0l, points[1].x = x0r,
	    points[0].y = points[1].y = y0,
	    points[2].x = x1r, points[3].x = x1l,
	    points[2].y = points[3].y = y1;
#undef y0
#undef y1
    if (vdev->bbox_device) {
	int code = (*dev_proc(vdev->bbox_device, fill_trapezoid))
	((gx_device *) vdev->bbox_device, left, right, ybot, ytop,
	 swap_axes, pdevc, lop);

	if (code < 0)
	    return code;
    }
    return gdev_vector_write_polygon(vdev, points, 4, true,
				     gx_path_type_fill);
}

int
gdev_vector_fill_parallelogram(gx_device * dev,
		 fixed px, fixed py, fixed ax, fixed ay, fixed bx, fixed by,
		  const gx_device_color * pdevc, gs_logical_operation_t lop)
{
    fixed pax = px + ax, pay = py + ay;
    int code = update_fill(vdev, pdevc, lop);
    gs_fixed_point points[4];

    if (code < 0)
	return gx_default_fill_parallelogram(dev, px, py, ax, ay, bx, by,
					     pdevc, lop);
    /* Make sure we aren't being clipped. */
    code = gdev_vector_update_clip_path(vdev, NULL);
    if (code < 0)
	return code;
    if (vdev->bbox_device) {
	code = (*dev_proc(vdev->bbox_device, fill_parallelogram))
	    ((gx_device *) vdev->bbox_device, px, py, ax, ay, bx, by,
	     pdevc, lop);
	if (code < 0)
	    return code;
    }
    points[0].x = px, points[0].y = py;
    points[1].x = pax, points[1].y = pay;
    points[2].x = pax + bx, points[2].y = pay + by;
    points[3].x = px + bx, points[3].y = py + by;
    return gdev_vector_write_polygon(vdev, points, 4, true,
				     gx_path_type_fill);
}

int
gdev_vector_fill_triangle(gx_device * dev,
		 fixed px, fixed py, fixed ax, fixed ay, fixed bx, fixed by,
		  const gx_device_color * pdevc, gs_logical_operation_t lop)
{
    int code = update_fill(vdev, pdevc, lop);
    gs_fixed_point points[3];

    if (code < 0)
	return gx_default_fill_triangle(dev, px, py, ax, ay, bx, by,
					pdevc, lop);
    /* Make sure we aren't being clipped. */
    code = gdev_vector_update_clip_path(vdev, NULL);
    if (code < 0)
	return code;
    if (vdev->bbox_device) {
	code = (*dev_proc(vdev->bbox_device, fill_triangle))
	    ((gx_device *) vdev->bbox_device, px, py, ax, ay, bx, by,
	     pdevc, lop);
	if (code < 0)
	    return code;
    }
    points[0].x = px, points[0].y = py;
    points[1].x = px + ax, points[1].y = py + ay;
    points[2].x = px + bx, points[2].y = py + by;
    return gdev_vector_write_polygon(vdev, points, 3, true,
				     gx_path_type_fill);
}

#undef vdev
