/***********************************************************
Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts,
and the Massachusetts Institute of Technology, Cambridge, Massachusetts.

		    All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the names of Digital or MIT not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.  

DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

******************************************************************/
/* $Header: milines.c,v 1.39 87/06/10 23:53:03 drewry Exp $ */

/*
*
*  written by Brian Kelleher, John Danskin; 1986
*  warped by tdn; 1986
*
*  Draw fat lines using a convex polygon routine.
*/

#include <stdio.h>
extern double hypot();
#include "X.h"
#include "windowstr.h"
#include "Xprotostr.h"
#include "gcstruct.h"
#include "scrnintstr.h"
#include "miscstruct.h"
#include "pixmapstr.h"
#include "mifpoly.h"
#include "mi.h"
#include "opaque.h"

#define GCVALSALU	0
#define GCVALSFORE	1
#define GCVALSBACK	2
#define GCVALSWIDTH	3
#define GCVALSCAPSTYLE	4
#define GCVALSARCMODE	5
static long gcvals[] = {GXcopy, 1, 0, 0, 0, ArcChord};

#define SAMESIGN(a, b)  ((((a) >= 0) && ((b) >= 0)) ||\
		     (((a) <= 0) && ((b) <= 0)))

/* Mitered Joints - 
* draw wide polylines by finding the polygon that outlines
* each wide line segment, and drawing that. Uses GC's fill polygon routine.
*
* internal vertexes are joined with a mitre as in PostScript. 
* vertices at the beginning and end of the polyline
* are given the appropriate end unless they meet at the same point in which
* case they are mitred together.  */
void
miWideLine(pDrawable, pGC, mode, npt, pPts)
    DrawablePtr pDrawable;
    GCPtr	pGC;
    int		mode;
    int		npt;
    DDXPointPtr pPts;
{
    int		width = pGC->lineWidth;
    SppPointPtr pSppPts, pSppT;
    long	gcflags; 
    int		i, xOrg, yOrg, fTricky, arcmode;
    int		xMin, xMax, yMin, yMax, dx, dy;
    DrawablePtr	pDrawTo;
    GCPtr	pGCTo;


    /* make everything absolute */
    if (mode == CoordModePrevious)
    {
	DDXPointPtr pptTmp;
	int nptTmp;

	pptTmp = pPts + 1;
	nptTmp = npt - 1;
	while (nptTmp--)
	{
	    pptTmp->x += (pptTmp-1)->x;
	    pptTmp->y += (pptTmp-1)->y;
	    pptTmp++;
	}
    }
    switch(pGC->alu)
    {
      case GXclear:		/* 0 */
      case GXcopy:		/* src */
      case GXcopyInverted:	/* NOT src */
      case GXset:		/* 1 */
	fTricky = FALSE;
        xOrg = yOrg = 0;
	pDrawTo = pDrawable;
	pGCTo = pGC;
	if(pGCTo->arcMode != ArcChord)
	{
	    arcmode = pGCTo->arcMode;
	    ChangeGC(pGCTo, GCArcMode, &gcvals[GCVALSARCMODE]);
	    ValidateGC(pDrawTo, pGCTo);
	}
	else
	    arcmode = ArcChord;

	break;
      case GXand:		/* src AND dst */
      case GXandReverse:	/* src AND NOT dst */
      case GXandInverted:	/* NOT src AND dst */
      case GXnoop:		/* dst */
      case GXxor:		/* src XOR dst */
      case GXor	:		/* src OR dst */
      case GXnor:		/* NOT src AND NOT dst */
      case GXequiv:		/* NOT src XOR dst */
      case GXinvert:		/* NOT dst */
      case GXorReverse:		/* src OR NOT dst */
      case GXorInverted:	/* NOT src OR dst */
      case GXnand:		/* NOT src OR NOT dst */
	fTricky = TRUE;
	yMin = yMax = pPts[0].y;
	xMin = xMax = pPts[0].x;

	for (i = 1; i < npt; i++)
	{
	    xMin = min(xMin, pPts[i].x);
	    xMax = max(xMax, pPts[i].x);
	    yMin = min(yMin, pPts[i].y);
	    yMax = max(yMax, pPts[i].y);
	}
	xOrg = xMin - (width + 1)/2;
	yOrg = yMin - (width + 1)/2;
	dx = xMax - xMin + width + 1;
	dy = yMax - yMin + width + 1;
	pDrawTo = (DrawablePtr) (*pDrawable->pScreen->CreatePixmap)
	  (pDrawable->pScreen, dx, dy, 1, XYBitmap);
	pGCTo = GetScratchGC(1, pDrawable->pScreen);
	gcvals[GCVALSWIDTH] = width;
	gcvals[GCVALSCAPSTYLE] = pGC->capStyle;
	gcflags = GCFunction|GCForeground|GCBackground|GCLineWidth |
		  GCCapStyle;
	if(pGCTo->arcMode != ArcChord)
	    gcflags |= GCArcMode;
	ChangeGC(pGCTo, gcflags, gcvals);
	ValidateGC(pDrawTo, pGCTo);
	ClearDrawable(pDrawTo, pGCTo, dx, dy);

    }
    pSppPts = (SppPointPtr) Xalloc((npt + 1)  * sizeof(SppPointRec));
    pSppT = pSppPts;
    /* convert points to DblPts */
    for(i = 0; i < npt; i++)
    {
	pSppT->x = (double) pPts->x;
	pSppT++->y = (double) pPts++->y;
	
    }

    /* Draw the lines with the appropriate join style.
     * We always wants caps drawn. */
    (*pGC->LineHelper) (pDrawTo, pGCTo, TRUE, npt, pSppPts, xOrg, yOrg);

    if(arcmode != ArcChord)
    {
	ChangeGC(pGCTo, GCArcMode, &gcvals[GCVALSARCMODE]);
	ValidateGC(pDrawTo, pGCTo);
    }
    if(fTricky)
    {
	if (pGC->miTranslate && (pDrawable->type == DRAWABLE←WINDOW) )
	{
	    xOrg += ((WindowPtr)pDrawable)->absCorner.x;
	    yOrg += ((WindowPtr)pDrawable)->absCorner.y;
	}

	(*pGC->PushPixels)(pGC, pDrawTo, pDrawable, dx, dy, xOrg, yOrg);
	(*pDrawable->pScreen->DestroyPixmap)(pDrawTo);
	FreeScratchGC(pGCTo);
    }
}

/* Draw wide line which only has 1 segment.  This is so easy that it's worth
 * special casing. Regardless of the raster op, this can't self-intersect.
 * Also handles fluke cases where we're called with only 1 point 
 */
void
miOneSegWide(pDrawable, pGC, npt, pPts, caps, xOrg, yOrg)
    DrawablePtr pDrawable;
    GCPtr	pGC;
    int		npt;
    SppPointPtr pPts;
    int		caps, xOrg, yOrg;
{
    SppPointPtr	pSppPts;
    SppPointRec	PolyPoints[4];
    int		i;
    int		width = pGC->lineWidth;

    if(npt == 1)
    {
	Xrealloc(pPts, 2 * sizeof(SppPointRec));
        pPts[1] = pPts[0];
    }
    if(caps && pGC->capStyle == CapProjecting)
    {
	if(PtEqual(pPts[0], pPts[1]))
	{
	    pPts[0].y -= width/2.0;
	    pPts[1].y += width/2.0;
	}
	else
	{
	    pPts[0] = ExtendSegment(pPts[0], pPts[1], width/2);
	    pPts[1] = ExtendSegment(pPts[1], pPts[0], width/2);
	}
    }
    GetPts(pPts[0], pPts[1], &PolyPoints[0], 
	   &PolyPoints[1], &PolyPoints[2], &PolyPoints[3], width);
    miFillSppPoly(pDrawable, pGC, 4, PolyPoints, -xOrg, -yOrg);
    if(caps && pGC->capStyle == CapRound)
    {
	RoundCap(pDrawable, pGC, pPts[0], pPts[1], PolyPoints[0],
	  PolyPoints[3], FirstEnd, xOrg, yOrg);
	RoundCap(pDrawable, pGC, pPts[1], pPts[0], PolyPoints[2],
	  PolyPoints[1], SecondEnd, xOrg, yOrg);
    }
}

void
miMiter (pDraw, pGC, caps, npt, pPts, xOrg, yOrg)
    DrawablePtr pDraw;
    GCPtr	pGC;
    int		caps;
    int		npt;
    SppPointPtr pPts;
    int		xOrg, yOrg;
{
    int		width = pGC->lineWidth;
    SppPointRec	PolyPoints[4], FirstEdge[2];
    SppPointRec	p1, p2, p3, p4, p5, p6, p7, p8;
    int		edges←match, i,
		capStyle = pGC->capStyle;

    /*
     * special case length 1 polyline
     */
    if(npt <= 2)
    {
        miOneSegWide(pDraw, pGC, npt, pPts, caps, xOrg, yOrg);
	return;
    }
    GetPts(pPts[0], pPts[1], &p1, &p2, &p3, &p4, width);

    /*
     * start on first edge of first point
     */
    if (PtEqual(pPts[0], pPts[npt-1]))
    {
	/*
	 * edges match so mitre them
	 */
	edges←match = 1;
	GetPts(pPts[npt-2], pPts[npt-1], &p5, &p6, &p7, &p8, width);

	PolyPoints[3] = IntersectLines(p1, p2, p5, p6);
	PolyPoints[2] = IntersectLines(p3, p4, p7, p8);

	FirstEdge[0] = PolyPoints[3];
	FirstEdge[1] = PolyPoints[2];
    }
    else
    {
	edges←match = 0;
	PolyPoints[3] = p1;
	PolyPoints[2] = p4;
	if (caps && capStyle == CapProjecting)
	{
	    pPts[0] = ExtendSegment(pPts[0], pPts[1], width/2);
	    GetPts(pPts[0], pPts[1], &p1, &p2, &p3, &p4, width);
	    pPts[npt-1] = ExtendSegment(pPts[npt-1], pPts[npt-2], width/2);
	}
	else if (caps && capStyle == CapRound)
	{
	    RoundCap(pDraw, pGC, pPts[0], pPts[1], p1, p4, FirstEnd,
	             xOrg, yOrg);
	    GetPts(pPts[npt-1], pPts[npt-2], &p5, &p6, &p7, &p8, width);
	    RoundCap(pDraw, pGC, pPts[npt-1], pPts[npt-2], p5, p8, FirstEnd,
	             xOrg, yOrg);
	}
    }

    for (i = 1; i < (npt - 1); i++)
    {
	/*
	 * find the next edge
	 * build a polygon out of the next edge and the last one
	 * send the polygon
	 * bump to next
	 */
	GetPts(pPts[i], pPts[i+1], &p5, &p6, &p7, &p8, width);

	PolyPoints[0] = PolyPoints[3];
	PolyPoints[1] = PolyPoints[2];
	PolyPoints[2] = IntersectLines(p4, p3, p7, p8);
	PolyPoints[3] = IntersectLines(p1, p2, p5, p6);

	miFillSppPoly(pDraw, pGC, 4, PolyPoints, -xOrg, -yOrg);

	p1 = p5;  p2 = p6;  p3 = p7;  p4 = p8;

    }

    PolyPoints[0] = PolyPoints[3];
    PolyPoints[1] = PolyPoints[2];

    if (edges←match)
    {
	PolyPoints[2] = FirstEdge[1];
	PolyPoints[3] = FirstEdge[0];
    }
    else
    {
	GetPts(pPts[npt-2], pPts[npt-1], &p5, &p6, &p7, &p8, width);
	PolyPoints[2] = p7;
	PolyPoints[3] = p6;
    }
	
    miFillSppPoly(pDraw, pGC, 4, PolyPoints, -xOrg, -yOrg);
}



typedef struct {
    int		c;
    SppPointRec	v0, v1, v2, v3, v4;
    SppPointRec	p1, p2, p3, p4;
} POLYLINEINFO;

void
miNotMiter (pDraw, pGC, caps, npt, pPts, xOrg, yOrg)
    DrawablePtr pDraw;
    GCPtr	pGC;
    int		caps;
    int		npt;
    SppPointPtr pPts;
    int		xOrg, yOrg;
{
    int		i, c,
		width = pGC->lineWidth,
    		capStyle = pGC->capStyle,
    		joinStyle = pGC->joinStyle;
    Bool	edges←match;
    SppPointRec	FirstEdge[4],	/* 2 extra for special npt == 2 */
    		wedge[3],
    		p, p1, p2, p3, p4, p5, p6, p7, p8, center;
    POLYLINEINFO *plinfo;
    double	dx, dy;

    /*
     * special case length 1 polyline
     */
    if(npt <= 2)
    {
        miOneSegWide(pDraw, pGC, npt, pPts, caps, xOrg, yOrg);
	return;
    }
    if(!(plinfo = (POLYLINEINFO *) ALLOCATE←LOCAL(npt * sizeof(POLYLINEINFO))))
	return;
    GetPts(pPts[0], pPts[1], &p1, &p2, &p3, &p4, width);
    /* save the points, in case this is hard */
    plinfo[0].p1 = p1;
    plinfo[0].p2 = p2;
    plinfo[0].p3 = p3;
    plinfo[0].p4 = p4;

    /*
     * If ends meet, calculate angle of final intersection and points
     * of intersection for it.
     */
    if(PtEqual(pPts[0], pPts[npt-1]))
    {
	edges←match = TRUE;
	GetPts(pPts[npt-2], pPts[npt-1], &p5, &p6, &p7, &p8, width);

	c = (pPts[0].x - pPts[npt-2].x)*(pPts[1].y - pPts[0].y) -
	    (pPts[1].x - pPts[0].x)*(pPts[0].y - pPts[npt-2].y);
	plinfo[0].c = c;

	if(c > 0) 	/* it's a clockwise turn */
	{
	    p = IntersectLines(p1, p2, p5, p6);
	    plinfo[0].v0 = p4;
	    plinfo[0].v1 = p7;
	    plinfo[0].v2 = p;
	    FirstEdge[0] = p;
	    FirstEdge[1] = p7;
	    if(joinStyle == JoinRound)
	    {
		center.x = (p4.x + p7.x)/2;
		center.y = (p4.y + p7.y)/2;
		RoundCap(pDraw, pGC, center, p, p4, p7, NotEnd, xOrg, yOrg);
	    }
	}
	else if (c < 0)	/* counterclockwise */
	{
	    p = IntersectLines(p3, p4, p7, p8);
	    plinfo[0].v0 = p;
	    plinfo[0].v1 = p6;
	    plinfo[0].v2 = p1;
	    FirstEdge[0] = p6;
	    FirstEdge[1] = p;
	    if(joinStyle == JoinRound)
	    {
		center.x = (p6.x + p1.x)/2;
		center.y = (p6.y + p1.y)/2;
		RoundCap(pDraw, pGC, center, p, p1, p6, NotEnd, xOrg, yOrg);
	    }
	}
	else /* straight line intersection */
	{
	    plinfo[0].v1 = p4;
	    plinfo[0].v2 = p1;
	    FirstEdge[0] = p6;
	    FirstEdge[1] = p7;

	}
    }
    else	/* Ends don't meet */
    {
	if(caps)
	{
	    if(capStyle == CapProjecting)
	    {
		pPts[0] = ExtendSegment(pPts[0], pPts[1], width/2);
		GetPts(pPts[0], pPts[1], &p1, &p2, &p3, &p4, width);
		plinfo[0].p1 = p1;
		plinfo[0].p2 = p2;
		plinfo[0].p3 = p3;
		plinfo[0].p4 = p4;
		pPts[npt-1] = ExtendSegment(pPts[npt-1], pPts[npt-2], width/2);
	    } else if (capStyle == CapRound)
	    {
		RoundCap(pDraw, pGC, pPts[0], pPts[1], p1, p4, FirstEnd,
			 xOrg, yOrg);
		GetPts(pPts[npt-1], pPts[npt-2], &p5, &p6, &p7, &p8, width);
		RoundCap(pDraw, pGC, pPts[npt-1], pPts[npt-2], p5, p8,
			 FirstEnd, xOrg, yOrg);
	    }
	}
	edges←match = FALSE;
	plinfo[0].c = 0;	/* treat no connection as straight connection */
	plinfo[0].v1 = p4;
	plinfo[0].v2 = p1;

    }

    for (i = 1; i < (npt - 1); i++)
    {
	/* Find the point of intersection with the previous segment.
	 * Make sure it's on this segment */
	if(plinfo[i-1].c > 0)
	{
	    p = plinfo[i-1].v2;
	    dx = p.x - p1.x;
	    dy = p.y - p1.y;
	}
	else if (plinfo[i-1].c < 0)
	{
	    p = plinfo[i-1].v0;
	    dx = p.x - p4.x;
	    dy = p.y - p4.y;
	}
	else
	{
	    dx = dy = 0;
	}
	GetPts(pPts[i], pPts[i+1], &p5, &p6, &p7, &p8, width);
	
	plinfo[i].p1 = p5;
	plinfo[i].p2 = p6;
	plinfo[i].p3 = p7;
	plinfo[i].p4 = p8;

	/* Figure out whether this angle turns clockwise or not by taking
	 * the total cross product of the three vectors formed by drawing
	 * vectors between the start of the previous segment and the start
	 * and end of this one */
	if(i == npt - 1)
	    c = 0;
	else
	    c = (pPts[i].x - pPts[i-1].x)*(pPts[i + 1].y - pPts[i].y) -
		 (pPts[i + 1].x - pPts[i].x)*(pPts[i].y - pPts[i-1].y);
	plinfo[i].c = c;


	if(c > 0) 	/* counter-clockwise */		
	{
	    p = IntersectLines(p1, p2, p5, p6);
	    plinfo[i].v0 = p8;
	    plinfo[i].v1 = p3;
	    plinfo[i].v2 = p;

	    plinfo[i-1].v3 = p;
	    plinfo[i-1].v4 = p3;
	    dx = p.x - p1.x;
	    dy = p.y - p1.y;
	    if(joinStyle == JoinRound)
	    {
		center.x = (p8.x + p3.x)/2;
		center.y = (p8.y + p3.y)/2;
		RoundCap(pDraw, pGC, center, p, p8, p3, NotEnd, xOrg, yOrg);
	    }
	}
	else if (c < 0)	
	{
	    p = IntersectLines(p3, p4, p7, p8);
	    plinfo[i].v0 = p;
	    plinfo[i].v1 = p2;
	    plinfo[i].v2 = p5;

	    plinfo[i-1].v3 = p2;
	    plinfo[i-1].v4 = p;
	    dx = p.x - p4.x;
	    dy = p.y - p4.y;
	    if(joinStyle == JoinRound)
	    {
		center.x = (p2.x + p5.x)/2;
		center.y = (p2.y + p5.y)/2;
		RoundCap(pDraw, pGC, center, p, p2, p5, NotEnd, xOrg, yOrg);
	    }
	}
	else 			/* straight line intersection */
	{
	    plinfo[i].v1 = p2;
	    plinfo[i].v2 = p3;
	    plinfo[i-1].v3 = p2;
	    plinfo[i-1].v4 = p3;
            dx = pPts[i].x - pPts[i-1].x;
            dy = pPts[i].y - pPts[i-1].y;

	}

	p1 = p5;  p2 = p6;  p3 = p7;  p4 = p8;
    }

    /* Special case for final segment --
    */
    if(plinfo[i-1].c > 0)
    {
	p = plinfo[i-1].v2;
	dx = p.x - p1.x;
	dy = p.y - p1.y;
    }
    else if (plinfo[i-1].c < 0)
    {
	p = plinfo[i-1].v0;
	dx = p.x - p4.x;
	dy = p.y - p4.y;
    }
	else
    {
	dx = dy = 0;
    }

    if (edges←match)
    {
	plinfo[i-1].v3 = FirstEdge[0];
	plinfo[i-1].v4 = FirstEdge[1];
	if(plinfo[i-1].c > 0)
	{
	    p = plinfo[i-1].v2;
	    dx = p.x - p1.x;
	    dy = p.y - p1.y;
	       
	}
	else if (plinfo[i-1].c < 0)
	{
	    p = plinfo[i-1].v0;
	    dx = p.x - p4.x;
	    dy = p.y - p4.y;
	}
	else
	{
            dx = (double) (pPts[npt-1].x - pPts[npt-2].x);
            dy = (double) (pPts[npt-1].y - pPts[npt-2].y);
	}
    }
    else
    {
	GetPts(pPts[npt-2], pPts[npt-1], &p5, &p6, &p7, &p8, width);
	plinfo[i-1].v3 = p6;
	plinfo[i-1].v4 = p7;
    }
    /*
     * plinfo[i].c is 0 if there is no notch, either because the lines meet
     * at 180 degrees, or the lines don't meet.  
     * Since we've set the alu to GCCopy, we don't worry about drawing
     * points twice.
     */
    for(i = 0; i < (npt - 1); i++)
    {
	if(plinfo[i].c < 0)
	{
	    wedge[1] = plinfo[i].p1;
	    wedge[2] = i ? plinfo[i - 1].p2 : plinfo[npt-2].p2;
	}
	else if (plinfo[i].c > 0)
	{
	    wedge[1] = plinfo[i].p4;
	    wedge[2] = i ? plinfo[i-1].p3 : plinfo[npt-2].p3;
	}

	if(plinfo[i].c)
	{
	    wedge[0] = pPts[i];
	    /* Check that points aren't colinear */
	    if(!(wedge[0].x == wedge[1].x && wedge[1].x == wedge[2].x) &&
	       !(wedge[0].y == wedge[1].y && wedge[1].y == wedge[2].y))
	    {
		miFillSppPoly(pDraw, pGC, 3, wedge, -xOrg, -yOrg);
	    }
	}
	miFillSppPoly(pDraw, pGC, 4, &plinfo[i].p1, -xOrg, -yOrg);
    }
    DEALLOCATE←LOCAL(plinfo);	
}

/*
*  Get the points associated with the endpoints
*  of a line, with the given width.
*/
void
GetPts(p1In, p2In, p1Out, p2Out, p3Out, p4Out, width)
    SppPointRec p1In, p2In;
    SppPointPtr p1Out, p2Out, p3Out, p4Out;
    int width;
{
    double hyp;         /* true distance of line (hypotenuse) */
    double dx, dy;
    double tmpdx, tmpdy;

    tmpdx = (double)(p1In.x - p2In.x);
    tmpdy = (double)(p1In.y - p2In.y);

    hyp = hypot(tmpdx, tmpdy);
    if (!ISZERO(hyp))
    {
    /* NOTE: the division by 2 must be done in floating point */
	dy = (tmpdx * width/2.0) / hyp;
	dx = (tmpdy * width/2.0) / hyp;

	p1Out->x = (p1In.x + dx);
	p1Out->y = (p1In.y - dy);

	p2Out->x = (p2In.x + dx);
	p2Out->y = (p2In.y - dy);

	p3Out->x = (p2In.x - dx);
	p3Out->y = (p2In.y + dy);

	p4Out->x = (p1In.x - dx);
	p4Out->y = (p1In.y + dy);
    }
    else
    {    
	/* Bizarre answer, but it's what the spec calls for */
	p1Out->x = p2Out->x = p1In.x - width/2.0;
	p3Out->x = p4Out->x = p1In.x + width/2.0;
	p1Out->y = p2Out->y = p3Out->y = p4Out->y = p1In.y;
    }
}

/*
*  Return the intersection of the line from p1 to p2 with
*  the line from p3 to p4.
*
*  First, reduce the lines to:   y = m1x + b,  and y = m2x + c.
*  Then do the standard math.
*/
SppPointRec
IntersectLines(p1, p2, p3, p4)
    SppPointRec p1, p2, p3, p4;
{
    SppPointRec i;
    double m1, m2;
    double b1, b2;
    double dx1, dx2;
    double tx;

    dx1 = p2.x - p1.x;
    dx2 = p4.x - p3.x;

    if (ISZERO(dx1))
    {
	if (! ISZERO(dx2))
	{
	    i.y = p3.y + ((p4.y - p3.y) * (p1.x - p3.x)) / (p4.x - p3.x);
	    i.x = p1.x;
	}
	else
	    i = p2;
    }
    else if (ISZERO(dx2))
    {
	i.y = p1.y + ((p2.y - p1.y) * (p3.x - p1.x)) / (p2.x - p1.x);
	i.x = p3.x;
    }
    else
    {
	m1 = (p2.y - p1.y) / dx1;
	m2 = (p4.y - p3.y) / dx2;

	b1 = p2.y - (m1 * p2.x);
	b2 = p4.y - (m2 * p4.x);

	if (! ISEQUAL(m1, m2))
	    i.x = (tx = ((b2-b1) / (m1-m2)));
	else
	    i.x = (tx = p2.x);

	i.y = m1 * tx + b1;
    }
    return(i);
}



/* Calcuate point p3 made by extending the segment backward (away from p2)
* by a length d */
SppPointRec
ExtendSegment(p1, p2, d)
    SppPointRec	p1, p2;
    int		d;
{
    SppPointRec	p3;
    double	l, dx, dy, hypot();

    dx = p2.x - p1.x;
    dy = p2.y - p1.y;
    l = hypot(dx, dy);
    if(!ISZERO(l))
    {
	p3.x = (p2.x - (l + d)/l * dx);
	p3.y = (p2.y - (l + d)/l * dy);
    }
    else
    {
	ErrorF("ExtendSegment called with coincident points.\n");
	p3 = p2;
    }
    return(p3);
}

/* Put Rounded cap on end. pCenter is the center of this end of the line
* pEnd is the center of the other end of the line. pCorner is one of the
* two corners at this end of the line.  It doesn't matter which of the two
* corners you give it. pOtherCorner is the other one.
*/
void
RoundCap(pDraw, pGC, pCenter, pEnd, pCorner, pOtherCorner, fLineEnd,
     xOrg, yOrg)
    DrawablePtr	pDraw;
    GCPtr	pGC;
    SppPointRec	pCenter, pEnd;
    SppPointRec	pCorner, pOtherCorner;
    int		fLineEnd, xOrg, yOrg;
{
    int		c, i, signc, cpt;
    double	width;
    double	dx, dy, hypot();
    xArc	arc;
    DDXPointPtr	pArcPts;
    SppPointPtr	pPts;
    long	arcmode = pGC->arcMode;

    if(arcmode != ArcChord)
    {
	ChangeGC(pGC, GCArcMode, &gcvals[GCVALSARCMODE]);
	ValidateGC(pDraw, pGC);
    }
    if (fLineEnd == NotEnd)
    {
	dx = pCenter.x - pCorner.x;
	dy = pCenter.y - pCorner.y;
	width = hypot(dx, dy);
    }
    else
	width = pGC->lineWidth;
    if(PtEqual(pCenter, pEnd))
    {
	signc = 1;
    }
    else
    {
	c = (pCenter.x - pEnd.x) * (pCorner.y - pCenter.y) -
	     (pCorner.x - pCenter.x) * (pCenter.y - pEnd.y);
	signc = -sign(c);
    }

    /* These come back scaled by 64 and all ready to use */
    arc.x = ROUNDTOINT(pCenter.x - width/2);
    arc.y = ROUNDTOINT(pCenter.y - width/2);
    arc.width = ROUNDTOINT(width);
    arc.height = arc.width;
    arc.angle1 = PtToAngle(pCenter, pCorner);
    arc.angle2 = signc * 180 * 64;
    pArcPts = (DDXPointPtr)NULL;
    if( cpt = miGetArcPts(&arc, 0, &pArcPts))
    {
	/* by drawing with miFillSppPoly and setting the endpoints of the arc
	 * to be the corners, we assure that the cap will meet up with the
	 * rest of the line */
	pPts = (SppPointPtr) Xalloc(cpt * sizeof(SppPointRec));
	for(i = 1; i < cpt -1; i++)
	{
	    pPts[i].x = (double) pArcPts[i].x;
	    pPts[i].y = (double) pArcPts[i].y;
	}
	pPts[0] = pCorner;
	pPts[cpt - 1] = pOtherCorner;
	miFillSppPoly(pDraw, pGC, cpt, pPts, -xOrg, -yOrg);
	Xfree((char *)pArcPts);
	Xfree((char *)pPts);
	if(arcmode != ArcChord)
	{
	    ChangeGC(pGC, GCArcMode, &arcmode);
	    ValidateGC(pDraw, pGC);
	}
    }
}