/*
 * DRCbasic.c --
 *
 * This file provides routines that make perform basic design-rule
 * checking:  given an area of a cell definition, this file will
 * find all of the rule violations and call a client procedure for
 * each one.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * 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.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef	lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/drc/DRCbasic.c,v 1.7 2010/09/20 21:13:22 tim Exp $";
#endif	/* not lint */

#include <sys/types.h>
#include <stdio.h>
#include <string.h>		// for memcpy()
#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "drc/drc.h"
#include "utils/signals.h"
#include "utils/maxrect.h"
#include "utils/malloc.h"

int dbDRCDebug = 0;

/* The following DRC cookie is used when there are tiles of type
 * TT_ERROR_S found during the basic DRC.  These arise during
 * hierarchical checking when there are illegal overlaps.
 */

static DRCCookie drcOverlapCookie = {
    0, 0, 0, 0,
    { 0 }, { 0 },
    0, 0, 0,
    DRC_OVERLAP_TAG,
    (DRCCookie *) NULL
};

/* Forward references: */

extern int areaCheck();
extern int drcTile();
extern MaxRectsData *drcCanonicalMaxwidth();

/*
 *-----------------------------------------------------------------------
 *
 * point_to_segment
 *
 *	Euclidean-distance point-to-segment distance (squared)
 *	calculation (borrowed from XCircuit)
 *
 * Results:
 *	Squared Euclidean distance of the closest approach of the
 *	line segment to the point (long result).
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */

long
point_to_segment(px, py, s1x, s1y, s2x, s2y)
    int px, py;		/* The position of the point */
    int s1x, s1y;	/* One endpoint of the line segment */
    int s2x, s2y;	/* The other endpoint of the line segment */
{
    long x, y;
    long a, b, c, frac;
    float protod;

    x = (long)s2x - (long)s1x;
    y = (long)s2y - (long)s1y;
    c = (x * x + y * y);

    x = (long)px - (long)s1x;
    y = (long)py - (long)s1y;
    a = (x * x + y * y);

    x = (long)px - (long)s2x;
    y = (long)py - (long)s2y;
    b = (x * x + y * y);

    frac = a - b;
    if (frac >= c) return b;
    else if (-frac >= c) return a;
    else
    {
	protod = (float)(c + a - b);
	return (a - (long)((protod * protod) / (float)(c << 2)));
    }
}

/* Define Euclidean distance checks */

#define RADIAL_NW	0x1000
#define RADIAL_NE	0x8000
#define RADIAL_SW	0x2000
#define RADIAL_SE	0x4000


/*
 * ----------------------------------------------------------------------------
 *
 * areaCheck --
 *
 * Call the function passed down from DRCBasicCheck() if the current tile
 * violates the rule in the given DRCCookie.  If the rule's connectivity
 * flag is set, then make sure the violating material isn't connected
 * to what's on the initial side of the edge before calling the client
 * error function.
 *
 * This function is called from DBSrPaintArea().
 *
 * Results:
 *	Zero (so that the search will continue).
 *
 * Side effects:
 *      Applies the function passed as an argument.
 *
 * ----------------------------------------------------------------------------
 */

int
areaCheck(tile, arg)
    Tile *tile;
    struct drcClientData *arg;
{
    Rect rect;		/* Area where error is to be recorded. */

    TiToRect(tile, &rect);

    /* Only consider the portion of the suspicious tile that overlaps
     * the clip area for errors, unless this is a trigger rule.
     */

    if (!(arg->dCD_cptr->drcc_flags & DRC_TRIGGER))
	GeoClip(&rect, arg->dCD_clip);

    GeoClip(&rect, arg->dCD_constraint);
    if ((rect.r_xbot >= rect.r_xtop) || (rect.r_ybot >= rect.r_ytop))
	return 0;

    /*
     * When Euclidean distance checks are enabled, check for error tiles
     * outside of the perimeter of the circle in the corner extension area
     * that extends "sdist" from the corner of the edge.
     *
     * Also check the relatively rare case where the tile is inside the
     * circle perimeter, but only the corner of the triangle projects into
     * the error check rectangle, and is outside of the circle.
     */

    if (arg->dCD_radial != 0)
    {
	unsigned int i;
	int sqx, sqy;
	int sdist = arg->dCD_radial & 0xfff;
	long sstest, ssdist = sdist * sdist;

	if ((arg->dCD_radial & RADIAL_NW) != 0)
	{
	    if (((sqx = arg->dCD_constraint->r_xbot + sdist
			 - rect.r_xtop) >= 0) && ((sqy = rect.r_ybot
			- arg->dCD_constraint->r_ytop + sdist) >= 0)
			&& ((sqx * sqx + sqy * sqy) >= ssdist))
		return 0;
	    else if (IsSplit(tile) && !SplitDirection(tile) && !SplitSide(tile))
	    {
		sstest = point_to_segment(arg->dCD_constraint->r_xbot + sdist,
			arg->dCD_constraint->r_ytop - sdist,
			LEFT(tile), BOTTOM(tile), RIGHT(tile), TOP(tile));
		if (sstest > ssdist) return 0;
	    }
	}
	if ((arg->dCD_radial & RADIAL_NE) != 0)
	{
	    if (((sqx = rect.r_xbot - arg->dCD_constraint->r_xtop
			+ sdist) >= 0) && ((sqy = rect.r_ybot
			- arg->dCD_constraint->r_ytop + sdist) >= 0)
			&& ((sqx * sqx + sqy * sqy) >= ssdist))
		return 0;
	    else if (IsSplit(tile) && SplitDirection(tile) && SplitSide(tile))
	    {
		sstest = point_to_segment(arg->dCD_constraint->r_xtop - sdist,
			arg->dCD_constraint->r_ytop - sdist,
			LEFT(tile), TOP(tile), RIGHT(tile), BOTTOM(tile));
		if (sstest > ssdist) return 0;
	    }
	}
	if ((arg->dCD_radial & RADIAL_SW) != 0)
	{
	    if (((sqx = arg->dCD_constraint->r_xbot + sdist
			- rect.r_xtop) >= 0) &&
			((sqy = arg->dCD_constraint->r_ybot
			+ sdist - rect.r_ytop) >= 0)
			&& ((sqx * sqx + sqy * sqy) >= ssdist))
		return 0;
	    else if (IsSplit(tile) && SplitDirection(tile) && !SplitSide(tile))
	    {
		sstest = point_to_segment(arg->dCD_constraint->r_xbot + sdist,
			arg->dCD_constraint->r_ybot + sdist,
			LEFT(tile), TOP(tile), RIGHT(tile), BOTTOM(tile));
		if (sstest > ssdist) return 0;
	    }
	}
	if ((arg->dCD_radial & RADIAL_SE) != 0)
	{
	    if (((sqx = rect.r_xbot - arg->dCD_constraint->r_xtop
			+ sdist) >= 0) &&
			((sqy = arg->dCD_constraint->r_ybot
			+ sdist - rect.r_ytop) >= 0)
			&& ((sqx * sqx + sqy * sqy) >= ssdist))
		return 0;
	    else if (IsSplit(tile) && !SplitDirection(tile) && SplitSide(tile))
	    {
		sstest = point_to_segment(arg->dCD_constraint->r_xtop - sdist,
			arg->dCD_constraint->r_ybot + sdist,
			LEFT(tile), BOTTOM(tile), RIGHT(tile), TOP(tile));
		if (sstest > ssdist) return 0;
	    }
	}
    }

    if (arg->dCD_cptr->drcc_flags & DRC_TRIGGER)
    {
	Rect *newrlist;
	int entries = arg->dCD_entries;

	/* The following code allows the rect list to be expanded by	*/
	/* multiples of 8, when necessary.				*/

	arg->dCD_entries++;
	if (arg->dCD_rlist == NULL)
	    arg->dCD_rlist = (Rect *)mallocMagic(8 * sizeof(Rect));
	else if ((arg->dCD_entries & ~(entries | 7)) == arg->dCD_entries)
	{
	    newrlist = (Rect *)mallocMagic((arg->dCD_entries << 1) * sizeof(Rect));
	    memcpy((void *)newrlist, (void *)arg->dCD_rlist, (size_t)entries *
			sizeof(Rect));
	    freeMagic(arg->dCD_rlist);
	    arg->dCD_rlist = newrlist;
	}
	arg->dCD_rlist[arg->dCD_entries - 1] = rect;
    }
    else
    {
	(*(arg->dCD_function))(arg->dCD_celldef, &rect, arg->dCD_cptr,
			arg->dCD_clientData);
	(*(arg->dCD_errors))++;
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DRCBasicCheck --
 *
 * This is the top-level routine for basic design-rule checking.
 *
 * Results:
 *	Number of errors found.
 *
 * Side effects:
 *	Calls function for each design-rule violation in celldef
 *	that is triggered by an edge in rect and whose violation
 *	area falls within clipRect.  This routine makes a flat check:
 *	it considers only information in the paint planes of celldef,
 *	and does not expand children.  Function should have the form:
 *	void
 *	function(def, area, rule, cdarg)
 *	    CellDef *def;
 *	    Rect *area;
 *	    DRCCookie *rule;
 *	    ClientData cdarg;
 *	{
 *	}
 *
 *	In the call to function, def is the definition containing the
 *	basic area being checked, area is the actual area where a
 *	rule is violated, rule is the rule being violated, and cdarg
 *	is the client data passed through all of our routines.
 *
 * Note:
 *	If an interrupt occurs (SigInterruptPending gets set), then
 *	the basic will be aborted immediately.  This means the check
 *	may be incomplete.
 *
 * ----------------------------------------------------------------------------
 */

int
DRCBasicCheck (celldef, checkRect, clipRect, function, cdata)
    CellDef *celldef;	/* CellDef being checked */
    Rect *checkRect;	/* Check rules in this area -- usually two Haloes
			 * larger than the area where changes were made.
			 */
    Rect *clipRect;	/* Clip error tiles against this area. */
    void (*function)();	/* Function to apply for each error found. */
    ClientData cdata;	/* Passed to function as argument. */
{
    struct drcClientData arg;
    int	errors;
    int planeNum;

    if (DRCCurStyle == NULL) return 0;	/* No DRC, no errors */

    /*  Insist on top quality rectangles. */

    if ((checkRect->r_xbot >= checkRect->r_xtop)
	    || (checkRect->r_ybot >= checkRect->r_ytop))
	 return (0);

    errors = 0;

    arg.dCD_celldef = celldef;
    arg.dCD_rect = checkRect;
    arg.dCD_errors = &errors;
    arg.dCD_function = function;
    arg.dCD_clip = clipRect;
    arg.dCD_clientData = cdata;
    arg.dCD_rlist = NULL;
    arg.dCD_entries = 0;

    for (planeNum = PL_TECHDEPBASE; planeNum < DBNumPlanes; planeNum++)
    {
        arg.dCD_plane = planeNum;
	DBResetTilePlane(celldef->cd_planes[planeNum], DRC_UNPROCESSED);
        (void) DBSrPaintArea ((Tile *) NULL, celldef->cd_planes[planeNum],
		checkRect, &DBAllTypeBits, drcTile, (ClientData) &arg);
    }
    drcCifCheck(&arg);
    if (arg.dCD_rlist != NULL) freeMagic(arg.dCD_rlist);
    return (errors);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcTile --
 *
 * This is a search function invoked once for each tile in
 * the area to be checked.  It checks design rules along the left
 * and bottom of the given tile.  If the tile extends beyond the
 * clipping rectangle in any direction, then the boundary on that
 * side of the tile will be skipped.
 *
 * Results:
 *	Zero (so that the search will continue), unless an interrupt
 *	occurs, in which case 1 is returned to stop the check.
 *
 * Side effects:
 *	Calls the client's error function if errors are found.
 *
 * ----------------------------------------------------------------------------
 */

int
drcTile (tile, arg)
    Tile *tile;	/* Tile being examined */
    struct drcClientData *arg;
{
    DRCCookie *cptr;	/* Current design rule on list */
    Rect *rect = arg->dCD_rect;	/* Area being checked */
    Rect errRect;		/* Area checked for an individual rule */
    MaxRectsData *mrd;		/* Used by widespacing rule */
    TileTypeBitMask tmpMask, *rMask;
    bool trigpending;		/* Hack for widespacing rule */
    bool firsttile;
    int triggered;
    int cdist, dist, ccdist, result;

    arg->dCD_constraint = &errRect;

    /*
     * If we were interrupted, we want to
     * abort the check as quickly as possible.
     */
    if (SigInterruptPending) return 1;
    DRCstatTiles++;

    /* If this tile is an error tile, it arose because of an illegal
     * overlap between things in adjacent cells.  This means that
     * there's an automatic violation over the area of the tile.
     */

    if (TiGetType(tile) == TT_ERROR_S)
    {
	TiToRect(tile, &errRect);
	GeoClip(&errRect, rect);
        (*(arg->dCD_function)) (arg->dCD_celldef, &errRect,
	    &drcOverlapCookie, arg->dCD_clientData);
        (*(arg->dCD_errors))++;
    }

    if (IsSplit(tile))
    {
	/* Check rules for DRC_ANGLES rule and process */
	TileType tt = TiGetLeftType(tile);
	if (tt != TT_SPACE)
	{
	    for (cptr = DRCCurStyle->DRCRulesTbl[TT_SPACE][tt];
			cptr != (DRCCookie *) NULL; cptr = cptr->drcc_next)
		if (cptr->drcc_flags & DRC_ANGLES)
		{
		    drcCheckAngles(tile, arg, cptr);
		    break;
		}
	}
	tt = TiGetRightType(tile);
	if (tt != TT_SPACE)
	{
	    for (cptr = DRCCurStyle->DRCRulesTbl[TT_SPACE][tt];
			cptr != (DRCCookie *) NULL; cptr = cptr->drcc_next)
		if (cptr->drcc_flags & DRC_ANGLES)
		{
		    drcCheckAngles(tile, arg, cptr);
		    break;
		}
	}

        /* This drc is only for the left edge of the tile */
	if (SplitSide(tile)) goto checkbottom;
    }

    /*
     * Check design rules along a vertical boundary between two tiles.
     *
     *			      1 | 4
     *				T
     *				|
     *			tpleft	|  tile
     *				|
     *				B
     *			      2 | 3
     *
     * The labels "T" and "B" indicate pointT and pointB respectively.
     *
     * If a rule's direction is FORWARD, then check from left to right.
     *
     *	    * Check the top right corner if the 1x1 lambda square
     *	      on the top left corner (1) of pointT matches the design
     *	      rule's "corner" mask.
     *
     *	    * Check the bottom right corner if the rule says check
     *	      BOTHCORNERS and the 1x1 lambda square on the bottom left
     *	      corner (2) of pointB matches the design rule's "corner" mask.
     *
     * If a rule's direction is REVERSE, then check from right to left.
     *
     *	    * Check the bottom left corner if the 1x1 lambda square
     *	      on the bottom right corner (3) of pointB matches the design
     *	      rule's "corner" mask.
     *
     *	    * Check the top left corner if the rule says check BOTHCORNERS
     *	      and the 1x1 lambda square on the top right corner (4) of
     *	      pointT matches the design rule's "corner" mask.
     */

    if (LEFT(tile) >= rect->r_xbot)		/* check tile against rect */
    {
	Tile *tpleft, *tpl, *tpr;
	TileType tt, to;
	int edgeTop, edgeBot;
        int top = MIN(TOP(tile), rect->r_ytop);
        int bottom = MAX(BOTTOM(tile), rect->r_ybot);
	int edgeX = LEFT(tile);

	firsttile = TRUE;
	mrd = NULL;
        for (tpleft = BL(tile); BOTTOM(tpleft) < top; tpleft = RT(tpleft))
        {
	    /* Get the tile types to the left and right of the edge */

	    tt = TiGetLeftType(tile);
	    to = TiGetRightType(tpleft);

	    /* Don't check synthetic edges, i.e. edges with same type on
             * both sides.  Such "edges" have no physical significance, and
	     * depend on internal-details of how paint is spit into tiles.
	     * Thus checking them just leads to confusion.  (When edge rules
	     * involving such edges are encountered during technology read-in
	     * the user is warned that such edges are not checked).
	     */

	    if (tt == to) continue;

	    /*
	     * Go through list of design rules triggered by the
	     * left-to-right edge.
	     */
	    edgeTop = MIN(TOP (tpleft), top);
	    edgeBot = MAX(BOTTOM(tpleft), bottom);
	    if (edgeTop <= edgeBot)
		continue;

	    triggered = 0;
	    for (cptr = DRCCurStyle->DRCRulesTbl[to][tt]; cptr != (DRCCookie *) NULL;
			cptr = cptr->drcc_next)
	    {
		if (cptr->drcc_flags & DRC_ANGLES) continue;

		/* Find the rule distances according to the scale factor */
		dist = cptr->drcc_dist;
		cdist = cptr->drcc_cdist;
		trigpending = (cptr->drcc_flags & DRC_TRIGGER) ? TRUE : FALSE;

		/* drcc_edgeplane is used to avoid checks on edges	*/
		/* in more than one plane				*/

		if (arg->dCD_plane != cptr->drcc_edgeplane)
		{
		    if (trigpending) cptr = cptr->drcc_next;
		    continue;
		}

		DRCstatRules++;

		if (cptr->drcc_flags & DRC_AREA)
		{
		    if (firsttile)
			drcCheckArea(tile, arg, cptr);
		    continue;
		}

		if ((cptr->drcc_flags & (DRC_MAXWIDTH | DRC_BENDS)) ==
				(DRC_MAXWIDTH | DRC_BENDS))
		{
		    /* New algorithm --- Tim 3/6/05 */
		    Rect *lr;
		    int i;

		    if (!trigpending || (DRCCurStyle->DRCFlags
				& DRC_FLAGS_WIDEWIDTH_NONINCLUSIVE))
			cptr->drcc_dist++;

		    if (cptr->drcc_flags & DRC_REVERSE)
			mrd = drcCanonicalMaxwidth(tpleft, GEO_WEST, arg, cptr);
		    else if (firsttile)
			mrd = drcCanonicalMaxwidth(tile, GEO_EAST, arg, cptr);
		    if (!trigpending || (DRCCurStyle->DRCFlags
				& DRC_FLAGS_WIDEWIDTH_NONINCLUSIVE))
			cptr->drcc_dist--;
		    if (trigpending)
		    {
			if (mrd)
			{
			    if (cptr->drcc_cdist <= cptr->drcc_dist)
				triggered = mrd->entries;
		            else if ((edgeTop - edgeBot) >= cptr->drcc_cdist)
			    {
				/* Run-length rule */
				for (i = 0; i < mrd->entries; i++)
				{
				    lr = &mrd->rlist[i];
				    GeoClip(lr, arg->dCD_clip);
				    if ((lr->r_ytop - lr->r_ybot) > cptr->drcc_cdist)
				    {
					triggered = mrd->entries;
					break;
				    }
				}
				if (i == mrd->entries)
				    cptr = cptr->drcc_next;
			    }
			    else
				cptr = cptr->drcc_next;
			}
			else
			    cptr = cptr->drcc_next;
		    }
		    else if (mrd)
		    {
			for (i = 0; i < mrd->entries; i++)
			{
			    lr = &mrd->rlist[i];
			    GeoClip(lr, arg->dCD_clip);
			    if (!GEO_RECTNULL(lr))
			    {
				(*(arg->dCD_function)) (arg->dCD_celldef,
					lr, cptr, arg->dCD_clientData);
				(*(arg->dCD_errors))++;
			    }
			}
		    }
		    continue;
		}
		else if (cptr->drcc_flags & DRC_MAXWIDTH)
		{
		    /* bends_illegal option only */
		    if (firsttile)
			drcCheckMaxwidth(tile, arg, cptr);
		    continue;
		}

		if (cptr->drcc_flags & DRC_RECTSIZE)
		{
		    /* only checked for bottom-left tile in a rect area */
		    if (firsttile && !TTMaskHasType(&cptr->drcc_mask,
				TiGetRightType(BL(tile))) &&
				!TTMaskHasType(&cptr->drcc_mask,
				TiGetTopType(LB(tile))))
			drcCheckRectSize(tile, arg, cptr);
		    continue;
		}

		result = 0;
		arg->dCD_radial = 0;
		arg->dCD_entries = 0;
		do {
		    if (triggered)
		    {
			/* For triggered rules, we want to look	at the	*/
			/* clipped region found by the triggering rule	*/

			if (mrd)
			    errRect = mrd->rlist[--triggered];
			else
			    errRect = arg->dCD_rlist[--triggered];
			errRect.r_ytop += cdist;
			errRect.r_ybot -= cdist;
			if (errRect.r_ytop > edgeTop) errRect.r_ytop = edgeTop;
			if (errRect.r_ybot < edgeBot) errRect.r_ybot = edgeBot;
		    }
		    else
		    {
			errRect.r_ytop = edgeTop;
			errRect.r_ybot = edgeBot;
		    }

		    if (cptr->drcc_flags & DRC_REVERSE)
		    {
			/*
			 * Determine corner extensions.
			 */

			/* Find the point (3) to the bottom right of pointB */
			if (BOTTOM(tile) >= errRect.r_ybot) tpr = LB(tile);
			else tpr = tile;

			/* Also find point (2) to check for edge continuation */
			if (BOTTOM(tpleft) >= errRect.r_ybot)
			    for (tpl = LB(tpleft); RIGHT(tpl) < edgeX; tpl = TR(tpl));
			else tpl = tpleft;

			/* Make sure the edge stops at edgeBot */
			if ((TiGetTopType(tpl) != TiGetBottomType(tpleft)) ||
				(TiGetTopType(tpr) != TiGetBottomType(tile)))
			{
			    if (TTMaskHasType(&cptr->drcc_corner, TiGetTopType(tpr)))
		 	    {
				errRect.r_ybot -= cdist;
				if (DRCEuclidean)
				    arg->dCD_radial |= RADIAL_SW;
			    }
			}

			if (cptr->drcc_flags & DRC_BOTHCORNERS)
			{
			    /*
			     * Check the other corner
			     */

			    /* Find point (4) to the top right of pointT */
			    if (TOP(tile) <= errRect.r_ytop)
				for (tpr = RT(tile); LEFT(tpr) > edgeX; tpr = BL(tpr));
			    else tpr = tile;

			    /* Also find point (1) to check for edge continuation */
			    if (TOP(tpleft) <= errRect.r_ytop) tpl = RT(tpleft);
			    else tpl = tpleft;

			    /* Make sure the edge stops at edgeTop */
			    if ((TiGetBottomType(tpl) != TiGetTopType(tpleft)) ||
					(TiGetBottomType(tpr) != TiGetTopType(tile)))
			    {
			        if (TTMaskHasType(&cptr->drcc_corner,
					TiGetBottomType(tpr)))
				{
				    errRect.r_ytop += cdist;
				    if (DRCEuclidean)
					arg->dCD_radial |= RADIAL_NW;
				}
			    }
			}

			/*
			 * Just for grins, see if we could avoid a messy search
			 * by looking only at tpleft.
			 */
			errRect.r_xbot = edgeX - dist;
			if (cptr->drcc_flags & DRC_OUTSIDE) errRect.r_xbot--;
			if (LEFT(tpleft) <= errRect.r_xbot
				&& BOTTOM(tpleft) <= errRect.r_ybot
				&& TOP(tpleft) >= errRect.r_ytop
				&& arg->dCD_plane == cptr->drcc_plane
				&& TTMaskHasType(&cptr->drcc_mask,
				TiGetRightType(tpleft)))
			    continue;

			errRect.r_xtop = edgeX;
			if (cptr->drcc_flags & DRC_OUTSIDE) errRect.r_xtop -= dist;
			arg->dCD_initial = tile;
		    }
		    else  /* FORWARD */
		    {
			/*
			 * Determine corner extensions.
			 */

			/* Find the point (1) to the top left of pointT */
			if (TOP(tpleft) <= errRect.r_ytop) tpl = RT(tpleft);
			else tpl = tpleft;

			/* Also find point (4) to check for edge continuation */
			if (TOP(tile) <= errRect.r_ytop)
			    for (tpr = RT(tile); LEFT(tpr) > edgeX; tpr = BL(tpr));
			else tpr = tile;

			/* Make sure the edge stops at edgeTop */
			if ((TiGetBottomType(tpl) != TiGetTopType(tpleft)) ||
				(TiGetBottomType(tpr) != TiGetTopType(tile)))
			{
			    if (TTMaskHasType(&cptr->drcc_corner, TiGetBottomType(tpl)))
			    {
				errRect.r_ytop += cdist;
				if (DRCEuclidean)
				    arg->dCD_radial |= RADIAL_NE;
			    }
			}

			if (cptr->drcc_flags & DRC_BOTHCORNERS)
			{
			    /*
			     * Check the other corner
			     */

			    /* Find point (2) to the bottom left of pointB. */
			    if (BOTTOM(tpleft) >= errRect.r_ybot)
				for (tpl = LB(tpleft); RIGHT(tpl) < edgeX; tpl = TR(tpl));
			    else tpl = tpleft;

			    /* Also find point (3) to check for edge continuation */
			    if (BOTTOM(tile) >= errRect.r_ybot) tpr = LB(tile);
			    else tpr = tile;

			    /* Make sure the edge stops at edgeBot */
			    if ((TiGetTopType(tpl) != TiGetBottomType(tpleft)) ||
					(TiGetTopType(tpr) != TiGetBottomType(tile)))
			    {
				if (TTMaskHasType(&cptr->drcc_corner, TiGetTopType(tpl)))
				{
				    errRect.r_ybot -= cdist;
				    if (DRCEuclidean)
					arg->dCD_radial |= RADIAL_SE;
				}
			    }
			}

			/*
			 * Just for grins, see if we could avoid a messy search
			 * by looking only at tile.
			 */
			errRect.r_xtop = edgeX + dist;
			if (cptr->drcc_flags & DRC_OUTSIDE) errRect.r_xtop++;
			if (RIGHT(tile) >= errRect.r_xtop
				&& BOTTOM(tile) <= errRect.r_ybot
				&& TOP(tile) >= errRect.r_ytop
				&& arg->dCD_plane == cptr->drcc_plane
				&& TTMaskHasType(&cptr->drcc_mask,
				TiGetLeftType(tile)))
			    continue;

			errRect.r_xbot = edgeX;
			if (cptr->drcc_flags & DRC_OUTSIDE) errRect.r_xbot += dist;
			arg->dCD_initial= tpleft;
		    }
		    if (arg->dCD_radial)
		    {
			arg->dCD_radial &= 0xf000;
			arg->dCD_radial |= (0xfff & cdist);
		    }

		    DRCstatSlow++;
		    arg->dCD_cptr = cptr;
		    arg->dCD_entries = 0;
		    TTMaskCom2(&tmpMask, &cptr->drcc_mask);
		    TTMaskClearType(&tmpMask, TT_ERROR_S);
		    DBSrPaintArea((Tile *) NULL,
				arg->dCD_celldef->cd_planes[cptr->drcc_plane],
				&errRect, &tmpMask, areaCheck, (ClientData) arg);
		} while (triggered);

		if (arg->dCD_entries == 0)
		{
		    /* Trigger rule:  If rule check found errors,	*/
		    /* do the next rule.  Otherwise, skip it.		*/

		    if (trigpending)
			cptr = cptr->drcc_next;
		}
		else
		    triggered = arg->dCD_entries;
	    }
	    DRCstatEdges++;
	    firsttile = FALSE;
        }
    }

    /* This drc is only for the bottom edge of the tile */

checkbottom:
    if (IsSplit(tile))
	if (SplitSide(tile) == SplitDirection(tile)) return 0;

    /*
     * Check design rules along a horizontal boundary between two tiles.
     *
     *			 4	tile	    3
     *			--L----------------R--
     *			 1	tpbot	    2
     *
     * The labels "L" and "R" indicate pointL and pointR respectively.
     * If a rule's direction is FORWARD, then check from bottom to top.
     *
     *      * Check the top left corner if the 1x1 lambda square on the bottom
     *        left corner (1) of pointL matches the design rule's "corner" mask.
     *
     *      * Check the top right corner if the rule says check BOTHCORNERS and
     *        the 1x1 lambda square on the bottom right (2) corner of pointR
     *	      matches the design rule's "corner" mask.
     *
     * If a rule's direction is REVERSE, then check from top to bottom.
     *
     *	    * Check the bottom right corner if the 1x1 lambda square on the top
     *	      right corner (3) of pointR matches the design rule's "corner"
     *	      mask.
     *
     *	    * Check the bottom left corner if the rule says check BOTHCORNERS
     *	      and the 1x1 lambda square on the top left corner (4) of pointL
     *	      matches the design rule's "corner" mask.
     */

    if (BOTTOM(tile) >= rect->r_ybot)
    {
	Tile *tpbot, *tpx;
	TileType tt, to;
	int edgeLeft, edgeRight;
        int left = MAX(LEFT(tile), rect->r_xbot);
        int right = MIN(RIGHT(tile), rect->r_xtop);
	int edgeY = BOTTOM(tile);

	/* Go right across bottom of tile */
	firsttile = TRUE;
	mrd = NULL;
        for (tpbot = LB(tile); LEFT(tpbot) < right; tpbot = TR(tpbot))
        {
	    /* Get the tile types to the top and bottom of the edge */

	    tt = TiGetBottomType(tile);
	    to = TiGetTopType(tpbot);

	    /* Don't check synthetic edges, i.e. edges with same type on
             * both sides.  Such "edges" have no physical significance, and
	     * depend on internal-details of how paint is spit into tiles.
	     * Thus checking them just leads to confusion.  (When edge rules
	     * involving such edges are encountered during technology readin
	     * the user is warned that such edges are not checked).
	     */

	    if (tt == to) continue;

	    /*
	     * Check to insure that we are inside the clip area.
	     * Go through list of design rules triggered by the
	     * bottom-to-top edge.
	     */
	    edgeLeft = MAX(LEFT(tpbot), left);
	    edgeRight = MIN(RIGHT(tpbot), right);
	    if (edgeLeft >= edgeRight)
		continue;

	    triggered = 0;
	    for (cptr = DRCCurStyle->DRCRulesTbl[to][tt]; cptr != (DRCCookie *) NULL;
				cptr = cptr->drcc_next)
	    {
		if (cptr->drcc_flags & DRC_ANGLES) continue;

		/* Find the rule distances according to the scale factor */
		dist = cptr->drcc_dist;
		cdist = cptr->drcc_cdist;
		trigpending = (cptr->drcc_flags & DRC_TRIGGER) ? TRUE : FALSE;

		/* drcc_edgeplane is used to avoid checks on edges	*/
		/* in more than one plane				*/

		if (arg->dCD_plane != cptr->drcc_edgeplane)
		{
		    if (trigpending) cptr = cptr->drcc_next;
		    continue;
		}

		DRCstatRules++;

		/* top to bottom */

		if ((cptr->drcc_flags & (DRC_MAXWIDTH | DRC_BENDS)) ==
				(DRC_MAXWIDTH | DRC_BENDS))
		{
		    /* New algorithm --- Tim 3/6/05 */
		    Rect *lr;
		    int i;

		    if (!trigpending || (DRCCurStyle->DRCFlags
				& DRC_FLAGS_WIDEWIDTH_NONINCLUSIVE))
			cptr->drcc_dist++;

		    if (cptr->drcc_flags & DRC_REVERSE)
			mrd = drcCanonicalMaxwidth(tpbot, GEO_SOUTH, arg, cptr);
		    else if (firsttile)
			mrd = drcCanonicalMaxwidth(tile, GEO_NORTH, arg, cptr);
		    if (!trigpending || (DRCCurStyle->DRCFlags
				& DRC_FLAGS_WIDEWIDTH_NONINCLUSIVE))
			cptr->drcc_dist--;
		    if (trigpending)
		    {
			if (mrd)
			{
			    if (cptr->drcc_cdist <= cptr->drcc_dist)
				triggered = mrd->entries;
		            else if ((edgeRight - edgeLeft) >= cptr->drcc_cdist)
			    {
				/* Run-length rule */
				for (i = 0; i < mrd->entries; i++)
				{
				    lr = &mrd->rlist[i];
				    GeoClip(lr, arg->dCD_clip);
				    if ((lr->r_xtop - lr->r_xbot) > cptr->drcc_cdist)
				    {
					triggered = mrd->entries;
					break;
				    }
				}
				if (i == mrd->entries)
				    cptr = cptr->drcc_next;
			    }
			    else
				cptr = cptr->drcc_next;
			}
			else
			    cptr = cptr->drcc_next;
		    }
		    else if (mrd)
		    {
			for (i = 0; i < mrd->entries; i++)
			{
			    lr = &mrd->rlist[i];
			    GeoClip(lr, arg->dCD_clip);
			    if (!GEO_RECTNULL(lr))
			     {
				(*(arg->dCD_function)) (arg->dCD_celldef,
					lr, cptr, arg->dCD_clientData);
				(*(arg->dCD_errors))++;
			    }
			}
		    }
		    continue;
		}
		else if (cptr->drcc_flags & (DRC_AREA | DRC_RECTSIZE
				| DRC_MAXWIDTH))
		{
		    /* only have to do these checks in one direction */
		    if (trigpending) cptr = cptr->drcc_next;
		    continue;
		}

		result = 0;
		arg->dCD_radial = 0;
		arg->dCD_entries = 0;
		do {
		    if (triggered)
		    {
			/* For triggered rules, we want to look	at the	*/
			/* clipped region found by the triggering rule	*/

			if (mrd)
			    errRect = mrd->rlist[--triggered];
			else
			    errRect = arg->dCD_rlist[--triggered];
			errRect.r_xtop += cdist;
			errRect.r_xbot -= cdist;
			if (errRect.r_xtop > edgeRight) errRect.r_xtop = edgeRight;
			if (errRect.r_xbot < edgeLeft) errRect.r_xbot = edgeLeft;
		    }
		    else
		    {
			errRect.r_xbot = edgeLeft;
			errRect.r_xtop = edgeRight;
		    }

		    if (cptr->drcc_flags & DRC_REVERSE)
		    {
			/*
			 * Determine corner extensions.
			 * Find the point (3) to the top right of pointR
			 */
			if (RIGHT(tile) <= errRect.r_xtop)
			    for (tpx = TR(tile); BOTTOM(tpx) > edgeY; tpx = LB(tpx));
			else tpx = tile;

		 	if (TTMaskHasType(&cptr->drcc_corner, TiGetLeftType(tpx)))
			{
			    errRect.r_xtop += cdist;
			    if (DRCEuclidean)
				arg->dCD_radial |= RADIAL_SE;
			}

			if (cptr->drcc_flags & DRC_BOTHCORNERS)
			{
			    /*
			     * Check the other corner by finding the
			     * point (4) to the top left of pointL.
			     */

			    if (LEFT(tile) >= errRect.r_xbot) tpx = BL(tile);
			    else tpx = tile;

			    if (TTMaskHasType(&cptr->drcc_corner, TiGetRightType(tpx)))
			    {
				errRect.r_xbot -= cdist;
				if (DRCEuclidean)
				    arg->dCD_radial |= RADIAL_SW;
			    }
			}

			/*
			 * Just for grins, see if we could avoid
			 * a messy search by looking only at tpbot.
			 */
			errRect.r_ybot = edgeY - dist;
			if (cptr->drcc_flags & DRC_OUTSIDE) errRect.r_ybot--;
			if (BOTTOM(tpbot) <= errRect.r_ybot
				&& LEFT(tpbot) <= errRect.r_xbot
				&& RIGHT(tpbot) >= errRect.r_xtop
				&& arg->dCD_plane == cptr->drcc_plane
				&& TTMaskHasType(&cptr->drcc_mask,
				TiGetTopType(tpbot)))
			    continue;

			errRect.r_ytop = edgeY;
			if (cptr->drcc_flags & DRC_OUTSIDE) errRect.r_ytop -= dist;
			arg->dCD_initial = tile;
		    }
		    else  /* FORWARD */
		    {
			/*
			 * Determine corner extensions.
			 * Find the point (1) to the bottom left of pointL
			 */

			if (LEFT(tpbot) >= errRect.r_xbot)
			    for (tpx = BL(tpbot); TOP(tpx) < edgeY; tpx = RT(tpx));
			else tpx = tpbot;

			if (TTMaskHasType(&cptr->drcc_corner, TiGetRightType(tpx)))
			{
			    errRect.r_xbot -= cdist;
			    if (DRCEuclidean)
				arg->dCD_radial |= RADIAL_NW;
			}

			if (cptr->drcc_flags & DRC_BOTHCORNERS)
			{
			    /*
			     * Check the other corner by finding the
			     * point (2) to the bottom right of pointR.
			     */
			    if (RIGHT(tpbot) <= errRect.r_xtop) tpx = TR(tpbot);
			    else tpx = tpbot;

			    if (TTMaskHasType(&cptr->drcc_corner, TiGetLeftType(tpx)))
			    {
				errRect.r_xtop += cdist;
				if (DRCEuclidean)
				    arg->dCD_radial |= RADIAL_NE;
			    }
			}

			/*
			 * Just for grins, see if we could avoid
			 * a messy search by looking only at tile.
			 */
			errRect.r_ytop = edgeY + dist;
			if (cptr->drcc_flags & DRC_OUTSIDE) errRect.r_ytop++;
			if (TOP(tile) >= errRect.r_ytop
				&& LEFT(tile) <= errRect.r_xbot
				&& RIGHT(tile) >= errRect.r_xtop
				&& arg->dCD_plane == cptr->drcc_plane
				&& TTMaskHasType(&cptr->drcc_mask,
				TiGetBottomType(tile)))
			    continue;

			errRect.r_ybot = edgeY;
			if (cptr->drcc_flags & DRC_OUTSIDE) errRect.r_ybot += dist;
			arg->dCD_initial = tpbot;
		    }
		    if (arg->dCD_radial)
		    {
			arg->dCD_radial &= 0xf000;
			arg->dCD_radial |= (0xfff & cdist);
		    }

		    DRCstatSlow++;
		    arg->dCD_cptr = cptr;
		    arg->dCD_entries = 0;
		    TTMaskCom2(&tmpMask, &cptr->drcc_mask);
		    TTMaskClearType(&tmpMask, TT_ERROR_S);
		    DBSrPaintArea((Tile *) NULL,
				arg->dCD_celldef->cd_planes[cptr->drcc_plane],
				&errRect, &tmpMask, areaCheck, (ClientData) arg);
		} while (triggered);

		if (arg->dCD_entries == 0)
		{
		    /* Trigger rule:  If rule check found errors,	*/
		    /* do the next rule.  Otherwise, skip it.	*/

		    if (trigpending)
			cptr = cptr->drcc_next;
		}
		else
		    triggered = arg->dCD_entries;
	    }
	    DRCstatEdges++;
	    firsttile = FALSE;
        }
    }
    return (0);
}
