blob: 502d184a662a840b291ee96e0f2dd16ff7a9227e [file] [log] [blame]
/* Copyright (C) 1989, 1990 Aladdin Enterprises. All rights reserved.
Distributed by Free Software Foundation, Inc.
This file is part of Ghostscript.
Ghostscript is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY. No author or distributor accepts responsibility
to anyone for the consequences of using it or for whether it serves any
particular purpose or works at all, unless he says so in writing. Refer
to the Ghostscript General Public License for full details.
Everyone is granted permission to copy, modify and redistribute
Ghostscript, but only under the conditions described in the Ghostscript
General Public License. A copy of this license is supposed to have been
given to you along with Ghostscript so you can know your rights and
responsibilities. It should be in a file named COPYING. Among other
things, the copyright notice and this notice must be preserved on all
copies. */
/* gxstroke.c */
/* Path stroking procedures for GhostScript library */
#include "math_.h"
#include "gx.h"
#include "gserrors.h"
#include "gxfixed.h"
#include "gxarith.h"
#include "gxmatrix.h"
#include "gzstate.h"
#include "gzdevice.h"
#include "gzcolor.h" /* requires gxdevice.h */
#include "gzline.h"
#include "gzpath.h"
/* stroke_add uses the following global for its path: */
private gx_path stroke_path_body;
private gx_path *stroke_path;
/* Structure for a partial line (passed to the drawing routine). */
/* Two of these are required to do joins right. */
/* Each endpoint includes the two ends of the cap as well, */
/* and the deltas for square and round cap computation. */
typedef struct endpoint_s {
gs_fixed_point p; /* the end of the line */
gs_fixed_point co, ce; /* ends of the cap */
gs_fixed_point cdelta; /* cap_length or -cap_length */
} endpoint;
typedef struct partial_line_s {
endpoint o; /* starting coordinate */
endpoint e; /* ending coordinate */
gs_fixed_point width; /* one-half line width, */
/* perpendicular (in user space) */
/* to line */
gs_fixed_point cap_length; /* ditto, parallel to line */
int thin; /* true if minimum-width line */
} partial_line;
/* Procedures that stroke a partial_line (the first argument). */
/* If both partial_lines are non-null, the procedure creates */
/* an appropriate join; otherwise, the procedure creates an */
/* end cap. If the first int is 0, the procedure also starts with */
/* an appropriate cap. */
private int stroke_add(P4(int, partial_line *, partial_line *, gs_state *));
private int stroke_fill(P4(int, partial_line *, partial_line *, gs_state *));
/* Other forward declarations */
private int stroke(P3(gx_path *,
int (*)(P4(int, partial_line *, partial_line *, gs_state *)),
gs_state *));
private void compute_caps(P2(partial_line *, gs_state *));
private int add_capped(P4(gx_path *, gs_line_cap,
int (*)(P3(gx_path *, fixed, fixed)),
endpoint *));
/* Stroke a path for drawing or saving */
int
gx_stroke_fill(gx_path *ppath, gs_state *pgs)
{ int code;
stroke_path = 0;
code = stroke(ppath, stroke_fill, pgs);
if ( stroke_path ) /* set if filling needed */
{ if ( code >= 0 )
code = gx_fill_path(stroke_path, pgs->dev_color, pgs,
gx_rule_winding_number, (fixed)0);
gx_path_release(stroke_path);
}
return code;
}
int
gx_stroke_add(gx_path *ppath, gx_path *topath, gs_state *pgs)
{ stroke_path = topath;
return stroke(ppath, stroke_add, pgs);
}
/* Stroke a path. Call line_proc for each line segment. */
private int
stroke(gx_path *ppath,
int (*line_proc)(P4(int, partial_line *, partial_line *, gs_state *)),
gs_state *pgs)
{ subpath *psub;
int code;
dash_params *dash = &pgs->line_params->dash;
float *dashes = dash->pattern;
int dash_count = dash->pattern_size;
gx_path fpath;
int skewed = is_skewed(&pgs->ctm);
float line_width = pgs->line_params->width;
float xx = pgs->ctm.xx, yy = pgs->ctm.yy;
int always_thin;
float line_width_and_scale;
#ifdef DEBUG
if ( gs_debug['o'] )
{ line_params *lp = pgs->line_params;
int count = lp->dash.pattern_size;
int i;
printf("[o]half_width=%f, cap=%d, join=%d,\n",
lp->width, (int)lp->cap, (int)lp->join);
printf(" miter_limit=%f, miter_check=%f,\n",
lp->miter_limit, lp->miter_check);
printf(" dash pattern=%d", count);
for ( i = 0; i < count; i++ )
printf(",%f", lp->dash.pattern[i]);
printf(",\n offset=%f, init(ink_on=%d, index=%d, dist_left=%f)\n",
lp->dash.offset, lp->dash.init_ink_on, lp->dash.init_index,
lp->dash.init_dist_left);
}
#endif
if ( is_fzero(line_width) )
{ always_thin = 1;
/* Set the line_width to something non-zero -- hack! */
line_width = 0.01;
}
else if ( skewed )
always_thin = 0;
else
{ float xxa = xx, yya = yy;
if ( xxa < 0 ) xxa = -xxa;
if ( yya < 0 ) yya = -yya;
always_thin = (max(xxa, yya) * line_width < 0.75);
}
if ( !always_thin )
line_width_and_scale = line_width * (float)int2fixed(1);
/* Start by flattening the path. We should do this on-the-fly.... */
if ( !ppath->curve_count ) /* don't need to flatten */
{ psub = ppath->first_subpath;
if ( !psub ) return 0;
}
else
{ if ( (code = gx_path_flatten(ppath, &fpath, pgs->flatness)) < 0 ) return code;
psub = fpath.first_subpath;
}
while ( psub )
{ int segcount = psub->line_count; /* no curves left */
line_segment *pline = (line_segment *)(psub->next);
fixed x = psub->pt.x;
fixed y = psub->pt.y;
partial_line pl, pl_prev, pl_first;
int first = 0;
#define next_line(ex, ey)\
(pl.o.p.x = x, pl.o.p.y = y, pl.e.p.x = (ex), pl.e.p.y = (ey),\
(pl.thin ? 0 : (compute_caps(&pl, pgs), 0)),\
(first++ == 0 ? (pl_first = pl, 0) : 0),\
(index++ ? (*line_proc)(index - 2, &pl_prev, &pl, pgs) : 0),\
pl_prev = pl, 0)
char ink_on = dash->init_ink_on;
int dash_index = dash->init_index;
float dist_left = dash->init_dist_left;
int index = 0;
while ( segcount-- )
{ fixed sx = pline->pt.x;
fixed sy = pline->pt.y;
/* Compute the width parameters in device space. */
/* We work with unscaled values, for speed. */
fixed udx, udy;
float unscaled_dist;
if ( sx == x && sy == y ) /* degenerate */
goto no_line;
if ( !always_thin || dash_count )
{ gs_point dpt; /* unscaled */
udx = sx - x, udy = sy - y;
if ( skewed )
gs_idtransform(pgs,
(float)udx, (float)udy, &dpt);
else /* shortcut */
{ dpt.x = udx / xx;
dpt.y = udy / yy;
}
unscaled_dist =
sqrt(dpt.x * dpt.x + dpt.y * dpt.y);
if ( !always_thin )
{ float wl = line_width_and_scale /
unscaled_dist;
/* Construct the width vector in */
/* user space, still unscaled. */
dpt.x *= wl;
dpt.y *= wl;
/* We now compute both parallel */
/* and perpendicular half-widths, */
/* as deltas in device space. */
/* We use a fixed-point, unscaled */
/* version of gs_dtransform. */
/* The second computation folds in */
/* a 90-degree rotation counter- */
/* clockwise (in user space, */
/* before transforming). */
pl.width.x =
-(fixed)(dpt.y * xx);
pl.width.y =
(fixed)(dpt.x * yy);
if ( skewed )
pl.width.x +=
(fixed)(dpt.x * pgs->ctm.yx),
pl.width.y -=
(fixed)(dpt.y * pgs->ctm.xy);
pl.thin =
((pl.width.x < 0 ? -pl.width.x :
pl.width.x) +
(pl.width.y < 0 ? -pl.width.y :
pl.width.y)) < float2fixed(0.75);
if ( pl.thin )
{ pl.width.x = pl.width.y = 0;
pl.cap_length.x =
pl.cap_length.y = 0;
}
else
{ pl.cap_length.x =
(fixed)(dpt.x * xx);
pl.cap_length.y =
(fixed)(dpt.y * yy);
if ( skewed )
pl.cap_length.x +=
(fixed)(dpt.y * pgs->ctm.yx),
pl.cap_length.y +=
(fixed)(dpt.x * pgs->ctm.xy);
}
}
else
pl.width.x = pl.width.y = 0,
pl.cap_length.x = pl.cap_length.y = 0,
pl.thin = 1;
}
else
pl.width.x = pl.width.y = 0,
pl.cap_length.x = pl.cap_length.y = 0,
pl.thin = 1;
if ( dash_count )
{ /* Account for dash pattern */
float dist_total =
unscaled_dist * (1 / (float)int2fixed(1));
float dist = dist_total;
float dx = udx, dy = udy; /* unscaled */
while ( dist > dist_left )
{ /* We are using up the dash element */
float fraction = dist_left / dist_total; /* unscaled-inverse */
fixed nx = x + (fixed)(dx * fraction);
fixed ny = y + (fixed)(dy * fraction);
if ( ink_on )
next_line(nx, ny);
dist -= dist_left;
if ( !(ink_on = !ink_on) )
{ if ( index )
{ (*line_proc)(index - 1, &pl_prev, (partial_line *)0, pgs);
index = 0;
}
}
if ( ++dash_index == dash_count )
dash_index = 0;
dist_left = dashes[dash_index];
x = nx, y = ny;
}
if ( ink_on )
next_line(sx, sy);
dist_left -= dist;
}
else
next_line(sx, sy);
no_line: pline = (line_segment *)(pline->next);
x = sx, y = sy;
}
if ( index )
{ /* If closed, join back to start, else cap */
(*line_proc)(index - 1, &pl_prev,
(psub->closed && ink_on && dash->init_ink_on ?
&pl_first : (partial_line *)0), pgs);
}
psub = (subpath *)pline;
if ( stroke_path == &stroke_path_body )
{ /* Fill and release the accumulated path */
gx_fill_path(stroke_path, pgs->dev_color, pgs,
gx_rule_winding_number, (fixed)0);
gx_path_release(stroke_path);
stroke_path = 0;
}
}
if ( ppath->curve_count ) gx_path_release(&fpath);
return 0;
}
/* ------ Internal routines ------ */
/* Compute the intersection of two lines. This is a messy algorithm */
/* that somehow ought to be useful in more places than just here.... */
private void
line_intersect(
gs_fixed_point *pp1, /* point on 1st line */
gs_fixed_point *pd1, /* slope of 1st line (dx,dy) */
gs_fixed_point *pp2, /* point on 2nd line */
gs_fixed_point *pd2, /* slope of 2nd line */
gs_fixed_point *pi) /* return intersection here */
{ /* We don't have to do any scaling, the factors all work out right. */
float x1 = pp1->x, y1 = pp1->y;
float u1 = pd1->x, v1 = pd1->y;
float x2 = pp2->x, y2 = pp2->y;
float u2 = pd2->x, v2 = pd2->y;
double denom = u1 * v2 - u2 * v1;
double num1 = v1 * x1 - u1 * y1;
double num2 = v2 * x2 - u2 * y2;
pi->x = (fixed)( (u1 * num2 - u2 * num1) / denom );
pi->y = (fixed)( (v1 * num2 - v2 * num1) / denom );
#ifdef DEBUG
if ( gs_debug['o'] )
{ printf("[o]Intersect %f,%f(%f/%f) & %f,%f(%f/%f) ->\n",
fixed2float(pp1->x), fixed2float(pp1->y),
fixed2float(pd1->x), fixed2float(pd1->y),
fixed2float(pp2->x), fixed2float(pp2->y),
fixed2float(pd2->x), fixed2float(pd2->y));
printf(" %f,%f\n",
fixed2float(pi->x), fixed2float(pi->y));
}
#endif
}
#define lix plp->o.p.x
#define liy plp->o.p.y
#define litox plp->e.p.x
#define litoy plp->e.p.y
/* Draw a line on the device. */
private int
stroke_fill(int first,
register partial_line *plp, partial_line *nplp, gs_state *pgs)
{ if ( plp->thin )
{ /* Minimum-width line, don't have to be careful. */
/* We do have to check for the entire line being */
/* within the clipping rectangle. */
if ( gx_cpath_includes_rectangle(pgs->clip_path,
lix, liy, litox, litoy) )
return gz_draw_line_fixed(lix, liy, litox, litoy,
pgs->dev_color, pgs);
/* We didn't set up the endpoint parameters before, */
/* because the line was thin. Do it now. */
/****** THE FOLLOWING IS A HACK. IT IS PROBABLY WRONG. ******/
plp->width.x = float2fixed(0.25);
plp->width.y = float2fixed(0.25);
plp->cap_length.x = float2fixed(0.125);
plp->cap_length.y = float2fixed(0.125);
compute_caps(plp, pgs);
}
{ /* General case. */
/* Construct a path and hand it to the fill algorithm. */
if ( stroke_path == 0 )
{ /* We are rendering, and haven't run into the */
/* general case yet. Initialize the path. */
stroke_path = &stroke_path_body; /* set global for stroke_add */
gx_path_init(stroke_path, &pgs->memory_procs);
}
stroke_add(first, plp, nplp, pgs);
{ /****** PATCH ******/
if ( stroke_path == &stroke_path_body )
{ gx_fill_path(stroke_path, pgs->dev_color, pgs,
gx_rule_winding_number, (fixed)0);
gx_path_release(stroke_path);
stroke_path = 0;
}
}
}
return 0;
}
#undef lix
#undef liy
#undef litox
#undef litoy
/* Add a segment to the path. This handles all the complex cases. */
private int add_capped(P4(gx_path *, gs_line_cap, int (*)(P3(gx_path *, fixed, fixed)), endpoint *));
private int
stroke_add(int first,
register partial_line *plp, partial_line *nplp, gs_state *pgs)
{ gx_path *ppath = stroke_path;
int code;
if ( ppath == 0 ) return 0; /****** strokepath is NYI ******/
if ( plp->thin )
{ /* We didn't set up the endpoint parameters before, */
/* because the line was thin. Do it now. */
compute_caps(plp, pgs);
}
if ( (code = add_capped(ppath, (first == 0 ? pgs->line_params->cap : gs_cap_butt), gx_path_add_point, &plp->o)) < 0 )
return code;
if ( nplp == 0 )
{ code = add_capped(ppath, pgs->line_params->cap, gx_path_add_line, &plp->e);
}
else if ( pgs->line_params->join == gs_join_round )
{ code = add_capped(ppath, gs_cap_round, gx_path_add_line, &plp->e);
}
else if ( nplp->thin ) /* no join */
{ code = add_capped(ppath, gs_cap_butt, gx_path_add_line, &plp->e);
}
else /* join_bevel or join_miter */
{ gs_fixed_point jp1, jp2;
/* Set np to whichever of nplp->o.co or .ce */
/* is outside the current line, i.e., */
/* whichever is on the same side as the cap. */
float fwx = plp->width.x, fwy = plp->width.y;
float fdx = plp->e.cdelta.x, fdy = plp->e.cdelta.y;
float fnwx = nplp->width.x, fnwy = nplp->width.y;
int cside = (fdx * fwy >= fdy * fwx);
int nside = (fnwx * fwy >= fnwy * fwx);
gs_fixed_point *np = (cside == nside ? &nplp->o.ce : &nplp->o.co);
#ifdef DEBUG
if ( gs_debug['o'] )
printf("[o]e.cd-side=%d nw/side=%d\n", cside, nside);
#endif
/* Compute the join point. */
/* Initialize it for a bevel join. */
jp1.x = plp->e.co.x, jp1.y = plp->e.co.y;
jp2.x = plp->e.ce.x, jp2.y = plp->e.ce.y;
if ( pgs->line_params->join == gs_join_miter )
{ /* Check whether a miter join is appropriate. */
/* Let a, b be the angles of the two lines. */
/* We check tan(a-b) against the miter_check */
/* by using the following formula: */
/* If tan(a)=u1/v1 and tan(b)=u2/v2, then */
/* tan(a-b) = (u1*v2 - u2*v1) / (u1*u2 + v1*v2). */
/* We can do all the computations unscaled, */
/* because we're only concerned with ratios. */
fixed u1 = plp->e.cdelta.x, v1 = plp->e.cdelta.y;
fixed u2 = nplp->e.cdelta.x, v2 = nplp->e.cdelta.y;
float uf1, vf1, uf2, vf2;
float num, denom;
float check = pgs->line_params->miter_check;
/* We will want either tan(pi-a+b) or tan(pi-b+a)
* depending on the orientations of the lines.
*
* tan(pi - a + b) = -tan(a-b)
* tan(pi - b + a) = tan(a-b)
*
* First reflect both lines so that 1st
* line is in 1st quadrant.
*/
if ( u1 < 0 ) u1 = -u1, u2 = -u2;
if ( v1 < 0 ) v1 = -v1, v2 = -v2;
uf1 = u1, vf1 = v1, uf2 = u2, vf2 = v2;
num = uf2 * vf1 - uf1 * vf2;
denom = uf1 * uf2 + vf1 * vf2;
/* Now proceed based on the angle of the 2nd line */
if ( u2 >= 0 )
{ if ( v2 >= 0 )
{ /* 1st quadrant => negative tangent */
if ( num > 0 ) num = -num;
if ( denom < 0 ) denom = -denom;
}
else
{ /* 4th quadrant => use tan(b-a) */
num = -num;
if ( denom < 0 ) num = -num, denom = -denom;
}
}
else
{ if ( v2 >= 0 )
{ /* 2nd quadrant => use tan(a-b) */
if ( denom < 0 ) num = -num, denom = -denom;
}
else
{ /* 3rd quadrant => positive tangent */
if ( num < 0 ) num = -num;
if ( denom < 0 ) denom = -denom;
}
}
#ifdef DEBUG
if ( gs_debug['o'] )
{ printf("[o]Miter check: u1/v1=%f/%f, u2/v2=%f/%f,\n",
uf1, vf1, uf2, vf2);
printf(" num=%f, denom=%f, check=%f\n",
num, denom, check);
}
#endif
if ( num >= 0 ?
(check > 0 && num >= denom * check) :
(check > 0 || num >= denom * check) )
{ /* OK to use a miter join. */
/* Compute the intersection of */
/* the extended edge lines. */
/* Start by discovering which of */
/* plp->e.co or .ce is outside */
/* the next line. fwx and fwy are negated */
/* because the e-line goes in the opposite */
/* direction from the o-line. */
float fndx = nplp->o.cdelta.x,
fndy = nplp->o.cdelta.y;
int noside = (fndx * fnwy >= fndy * fnwx);
int wside = (-fwx * fnwy >= -fwy * fnwx);
#ifdef DEBUG
if ( gs_debug['o'] )
printf("[o]no.cd-side=%d, w-side=%d\n",
noside, wside);
#endif
if ( noside == wside )
line_intersect(&plp->e.ce, &plp->e.cdelta, np, &nplp->o.cdelta, &jp2);
else
line_intersect(&plp->e.co, &plp->e.cdelta, np, &nplp->o.cdelta, &jp1);
}
}
if ( (code = gx_path_add_line(ppath, jp1.x, jp1.y)) < 0 ||
(code = gx_path_add_line(ppath, np->x, np->y)) < 0 ||
(code = gx_path_add_line(ppath, jp2.x, jp2.y)) < 0
) return code;
}
if ( code < 0 || (code = gx_path_close_subpath(ppath)) < 0 )
return code;
return 0;
}
/* Routines for cap computations */
/* Compute the endpoints of the two caps of a segment. */
private void
compute_caps(register partial_line *plp, gs_state *pgs)
{ fixed wx2 = plp->width.x;
fixed wy2 = plp->width.y;
#define lx2 plp->cap_length.x
#define ly2 plp->cap_length.y
plp->o.co.x = plp->o.p.x - wx2, plp->o.co.y = plp->o.p.y - wy2;
plp->o.ce.x = plp->o.p.x + wx2, plp->o.ce.y = plp->o.p.y + wy2;
plp->e.co.x = plp->e.p.x + wx2, plp->e.co.y = plp->e.p.y + wy2;
plp->e.ce.x = plp->e.p.x - wx2, plp->e.ce.y = plp->e.p.y - wy2;
plp->o.cdelta.x = -lx2, plp->o.cdelta.y = -ly2;
plp->e.cdelta.x = lx2, plp->e.cdelta.y = ly2;
#ifdef DEBUG
if ( gs_debug['o'] )
printf("[o]Stroke o=(%f,%f) e=(%f,%f)\n",
fixed2float(plp->o.p.x), fixed2float(plp->o.p.y),
fixed2float(plp->e.p.x), fixed2float(plp->e.p.y)),
printf(" wxy=(%f,%f) lxy=(%f,%f)\n",
fixed2float(wx2), fixed2float(wy2),
fixed2float(lx2), fixed2float(ly2));
#endif
#undef lx2
#undef ly2
}
/* Add a properly capped line endpoint to the path. */
/* The first point may require either moveto or lineto. */
private int
add_capped(gx_path *ppath, gs_line_cap type,
int (*add_proc)(P3(gx_path *, fixed, fixed)), /* gx_path_add_point/line */
register endpoint *endp)
{ int code;
#define px endp->p.x
#define py endp->p.y
#define xo endp->co.x
#define yo endp->co.y
#define xe endp->ce.x
#define ye endp->ce.y
#define cdx endp->cdelta.x
#define cdy endp->cdelta.y
#ifdef DEBUG
if ( gs_debug['o'] )
printf("[o]cap: p=(%g,%g), co=(%g,%g), ce=(%g,%g), cd=(%g,%g)\n",
fixed2float(px), fixed2float(py),
fixed2float(xo), fixed2float(yo),
fixed2float(xe), fixed2float(ye),
fixed2float(cdx), fixed2float(cdy));
#endif
switch ( type )
{
case gs_cap_round:
{ fixed xm = px + cdx;
fixed ym = py + cdy;
if ( (code = (*add_proc)(ppath, xo, yo)) < 0 ||
(code = gx_path_add_arc(ppath, xo, yo, xm, ym,
xo + cdx, yo + cdy)) < 0 ||
(code = gx_path_add_arc(ppath, xm, ym, xe, ye,
xe + cdx, ye + cdy)) < 0
) return code;
}
break;
case gs_cap_square:
if ( (code = (*add_proc)(ppath, xo + cdx, yo + cdy)) < 0 ||
(code = gx_path_add_line(ppath, xe + cdx, ye + cdy)) < 0
) return code;
break;
case gs_cap_butt:
if ( (code = (*add_proc)(ppath, xo, yo)) < 0 ||
(code = gx_path_add_line(ppath, xe, ye)) < 0
) return code;
}
return code;
}