/*
 * PlowRules1.c --
 *
 * Plowing rules.
 * These are applied by plowProcessEdge() for each edge that is to be moved.
 *
 *     ********************************************************************* 
 *     * 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[]="$Header: /ufs/repository/magic/plow/PlowRules1.c,v 1.3 2001/06/15 15:33:40 tim Exp $";
#endif  not lint

#include <stdio.h>
#include "misc/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "undo/undo.h"
#include "plow/plow.h"
#include "plow/plowInt.h"
#include "drc/drc.h"

/* Forward declarations */
int plowPenumbraTopProc();
int plowPenumbraBotProc();
int plowSliverTopExtent(), plowSliverTopMove();
int plowSliverBotExtent(), plowSliverBotMove();
int plowApplyRule();
int plowPenumbraRule();

/*
 * ----------------------------------------------------------------------------
 *
 * prClearUmbra --
 *
 * SEARCH RULE.
 * Sweep all edges out of the umbra.
 * This rule preserves horizontal edge orderings within a plane.
 * Two segments that overlap in Y cannot cross each other in X:
 *
 *
 *	1		2
 *	1 ------------>	2	1 will force 2 to move
 *	1		2	regardless of design rules
 *
 *
 * Below, this rule doesn't apply, so 1 can slide past 2.
 *
 *	1
 *	1 ------------------>
 *	1
 *			2
 *			2
 *			2
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May add edges to the queue of edges to be processed.
 *
 * ----------------------------------------------------------------------------
 */

Void
prClearUmbra(edge)
    register Edge *edge;	/* Edge being moved */
{
    TileTypeBitMask rhsTypes;
    struct applyRule ar;

    TTMaskSetOnlyType(&rhsTypes, edge->e_rtype);
    ar.ar_moving = edge;
    ar.ar_rule = (PlowRule *) NULL;
    (void) plowSrShadow(edge->e_pNum, &edge->e_rect,
		rhsTypes, plowApplyRule, (ClientData) &ar);
}

/*
 * ----------------------------------------------------------------------------
 *
 * prUmbra --
 *
 * SEARCH RULE.
 * Apply width or spacing rules in the umbra.
 * The umbra of an edge is the area through which it moves from its
 * initial position (I) to its final position (F):
 *
 *	I ======================F-------D
 *	I			F	D
 *	I	  umbra		F  halo	D
 *	I			F	D
 *	I ======================F-------D
 *
 *	  --------------------> + --d-->
 *
 * For each rule in the list 'rules', we shadow-search the umbra plus an
 * additional area of width 'd' to the right (ending at (D) above), where
 * 'd' is the distance associated with the rule.  We queue to be moved the
 * edges of any material found that is not in the rule's pr_oktypes set.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May add edges to the queue of edges to be processed.
 *
 * ----------------------------------------------------------------------------
 */

Void
prUmbra(edge, rules)
    Edge *edge;		/* Edge being moved */
    PlowRule *rules;	/* List of rules */
{
    register PlowRule *pr;
    struct applyRule ar;
    Rect searchArea;

    ar.ar_moving = edge;
    searchArea = edge->e_rect;
    for (pr = rules; pr; pr = pr->pr_next)
    {
	ar.ar_rule = pr;
	searchArea.r_xtop = edge->e_newx + pr->pr_dist;
	(void) plowSrShadow(pr->pr_pNum, &searchArea,
			pr->pr_oktypes, plowApplyRule, (ClientData) &ar);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * prPenumbraTop --
 * prPenumbraBot --
 *
 * Search the penumbra of the supplied edge for each rule in the list
 * 'list', moving all edges necessary to insure that width or spacing
 * rules are satisfied.  The upper part of the penumbra is searched by
 * plowPenumbraTop, and the lower part by plowPenumbraBot.
 *
 * The penumbra of an edge (E) is that region above and below it, in the
 * direction the edge is moving.  The left-hand border of the penumbra,
 * and the distance it extends to the right of the final position of
 * the edge (F), are equal to the distance associated with the design
 * rule (d).
 *
 *   ^		T===============|
 *   |		T		|
 *   d		T		|
 *   |	TTTTTTTTT    upper	|
 *   v	T=======================|
 *	E		F
 *	E		F
 *	E ------------>	F <--d-->
 *	E		F
 *	E		F
 *   ^	BBBBBBBBB===============|
 *   |		B    lower	|
 *   d		BBBBBBBBB	|
 *   |			B	|
 *   v			B=======|
 *
 * The left-hand border of the penumbra (T, B) for a rule is not a
 * simple extension of the original edge E, but is instead the result
 * of following the outline of the material in the pr_ltypes set
 * associated with that rule.
 *
 * If the outline (O) turns left before reaching the top (bottom) of the
 * clip area, the remainder of the left-hand border (X) of the penumbra
 * is considered to extend from where the outline turned left, out to
 * the top (bottom) of the clip area:
 *
 *	     ^	X=======================+
 *  extension|	X
 *	     v	X
 *	OOOOOOOOO
 *		O
 *		O
 *	OOOOOOOOO=======================
 *	E
 *	E
 *	E
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May add edges to the queue of edges to be processed.
 *
 * Algorithm notes:
 *	We use plowSrOutline() to trace the penumbra outlines.  Since
 *	it moves counterclockwise around the outline of a collection of
 *	types, this is ideal for following the top penumbra's boundary.
 *	Ideally, we wish to follow the bottom penumbra's boundary in a
 *	clockwise direction instead.  To achieve this, we follow the
 *	outline of the complementary set of types in a counterclockwise
 *	direction.
 *
 * ----------------------------------------------------------------------------
 */

Void
prPenumbraTop(edge, rules)
    Edge *edge;		/* Edge being moved */
    PlowRule *rules;	/* Rules to apply (must be non-NULL) */
{
    register PlowRule *pr;
    struct applyRule ar;
    Point startPoint;

    ar.ar_moving = edge;
    startPoint.p_x = edge->e_x;
    startPoint.p_y = edge->e_ytop;
    for (pr = rules; pr; pr = pr->pr_next)
    {
	ar.ar_rule = pr;
	ar.ar_clip.p_x = edge->e_newx + pr->pr_dist;
	ar.ar_clip.p_y = edge->e_ytop + pr->pr_dist;
	plowSrOutline(edge->e_pNum, &startPoint, pr->pr_ltypes, GEO_NORTH,
		GMASK_WEST|GMASK_NORTH|GMASK_SOUTH,
		plowPenumbraTopProc, (ClientData) &ar);
    }
}

Void
prPenumbraBot(edge, rules)
    Edge *edge;		/* Edge being moved */
    PlowRule *rules;	/* Rules to apply (must be non-NULL) */
{
    TileTypeBitMask insideTypes;
    register PlowRule *pr;
    struct applyRule ar;
    Point startPoint;

    ar.ar_moving = edge;
    startPoint.p_x = edge->e_x;
    startPoint.p_y = edge->e_ybot;
    for (pr = rules; pr; pr = pr->pr_next)
    {
	ar.ar_rule = pr;
	ar.ar_clip.p_x = edge->e_newx + pr->pr_dist;
	ar.ar_clip.p_y = edge->e_ybot - pr->pr_dist;
	TTMaskCom2(&insideTypes, &pr->pr_ltypes);
	plowSrOutline(edge->e_pNum, &startPoint, insideTypes, GEO_SOUTH,
		GMASK_WEST|GMASK_NORTH|GMASK_SOUTH,
		plowPenumbraBotProc, (ClientData) &ar);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowPenumbraTopProc --
 * plowPenumbraBotProc --
 *
 * Process a vertical (GEO_NORTH or GEO_SOUTH) segment of the left-hand
 * boundary of the penumbra, for purposes of applying a width or spacing
 * rule.  Width and spacing rules for the upper part of the penumbra are
 * processed by plowPenumbraTopProc(); rules for the lower part are
 * processed by plowPenumbraBotProc().
 *
 * We expect the following fields of the applyRule struct 'ar' to be filled in:
 *
 *	ar_clip		-- boundary of the penumbra.  If a segment of the
 *			   penumbra's outline extends to the right of this
 *			   boundary point, we stop.  If a segment extends
 *			   above this point (if plowPenumbraTopProc), or
 *			   below this point (if plowPenumbraBotProc), we stop.
 *	ar_moving	-- edge being moved that caused this search.
 *	ar_rule		-- design rule (width or spacing) being applied.
 *
 * When processing the upper penumbra, the "inside" material is to the left,
 * and the "outside" to the right.  When processing the lower penumbra, the
 * situation is reversed because we are following the complementary material:
 * "inside" is to the right and "outside" to the left.  See the algorithm
 * notes for prPenumbraTop() and prPenumbraBot() above.
 *
 * Results:
 *	0 if plowSrOutline should keep going, 1 if not.
 *
 * Side effects:
 *	May add edges to the queue of edges to be processed.
 *
 * ----------------------------------------------------------------------------
 */

int
plowPenumbraTopProc(outline, ar)
    register Outline *outline;		/* Segment along penumbra border */
    register struct applyRule *ar;	/* Info needed for shadow search */
{
    register Edge *movingEdge = ar->ar_moving;
    register PlowRule *pr = ar->ar_rule;
    Rect searchArea;
    int ret = 0;

    /*
     * Test for immediate termination conditions:
     *		- a southward-bound edge
     *		- a northward-bound edge to the right of the clip point
     */
    if (outline->o_currentDir == GEO_SOUTH
	    || outline->o_rect.r_xbot >= ar->ar_clip.p_x)
	return (1);

    /*
     * Test for termination after processing this edge:
     *		- a northward-bound edge touching or crossing the top
     *		  of the clip area.
     * If this is true, clip the search area to the top of the clip area.
     */
    searchArea = outline->o_rect;
    if (searchArea.r_ytop >= ar->ar_clip.p_y)
	ret = 1, searchArea.r_ytop = ar->ar_clip.p_y;
    searchArea.r_xtop = movingEdge->e_newx + pr->pr_dist;

    /*
     * If we just turned left and haven't reached the outside of
     * the clip area, extend for one last shadow search.
     */
    if (outline->o_currentDir == GEO_WEST)
    {
	if (outline->o_rect.r_ytop < ar->ar_clip.p_y)
	{
	    searchArea.r_xbot = outline->o_rect.r_xtop - 1;
	    searchArea.r_ybot = outline->o_rect.r_ytop;
	    searchArea.r_ytop = ar->ar_clip.p_y;
	    (void) plowSrShadow(pr->pr_pNum,
		&searchArea, pr->pr_oktypes, plowPenumbraRule,
		(ClientData) ar);
	}
	return (1);
    }

    /* Shadow search to right of this segment of the penumbra boundary */
    (void) plowSrShadow(pr->pr_pNum, &searchArea,
		pr->pr_oktypes, plowApplyRule, (ClientData) ar);
    return (ret);
}

int
plowPenumbraBotProc(outline, ar)
    register Outline *outline;		/* Segment along penumbra border */
    register struct applyRule *ar;	/* Info needed for shadow search */
{
    register Edge *movingEdge = ar->ar_moving;
    register PlowRule *pr = ar->ar_rule;
    Rect searchArea;
    int ret = 0;

    /*
     * Test for immediate termination conditions:
     *		- a northward-bound edge
     *		- a southward-bound edge to the right of the clip point
     */
    if (outline->o_currentDir == GEO_NORTH
	    || outline->o_rect.r_xbot >= ar->ar_clip.p_x)
	return (1);

    /*
     * Test for termination after processing this edge:
     *		- a southward-bound edge touching or crossing the bottom
     *		  of the clip area.
     * If this is true, clip the search area to the bottom of the clip area.
     */
    searchArea = outline->o_rect;
    if (searchArea.r_ybot <= ar->ar_clip.p_y)
	ret = 1, searchArea.r_ybot = ar->ar_clip.p_y;
    searchArea.r_xtop = movingEdge->e_newx + pr->pr_dist;

    /*
     * If we just turned left and haven't reached the outside of
     * the clip area, extend for one last shadow search.
     */
    if (outline->o_currentDir == GEO_WEST)
    {
	if (outline->o_rect.r_ybot > ar->ar_clip.p_y)
	{
	    searchArea.r_xbot = outline->o_rect.r_xtop - 1;
	    searchArea.r_ybot = ar->ar_clip.p_y;
	    searchArea.r_ytop = outline->o_rect.r_ybot;
	    (void) plowSrShadow(pr->pr_pNum,
		&searchArea, pr->pr_oktypes, plowPenumbraRule,
		(ClientData) ar);
	}
	return (1);
    }

    /* Shadow search to right of this segment of the penumbra boundary */
    (void) plowSrShadow(pr->pr_pNum, &searchArea,
		pr->pr_oktypes, plowApplyRule, (ClientData) ar);

    return (ret);
}


/*
 * plowPenumbraRule --
 *
 * Like plowApplyRule, except we don't queue an edge whose LHS
 * is not in ar->ar_rule->pr_oktypes.  This should prevent the
 * penumbra extension search from finding bad edges.
 *
 * Results:
 *	Returns 0 always.
 *
 * Side effects:
 *	May queue the edge.
 */

plowPenumbraRule(impactedEdge, ar)
    register Edge *impactedEdge;	/* Edge found by shadow search */
    register struct applyRule *ar;	/* Edge causing the shadow search, and
					 * the design rule to apply.
					 */
{
    register PlowRule *pr;
    register Edge *movingEdge = ar->ar_moving;
    int newsep, oldsep, newx;

    oldsep = impactedEdge->e_x - movingEdge->e_x;
    if (pr = ar->ar_rule)
    {
	if (!TTMaskHasType(&pr->pr_oktypes, impactedEdge->e_ltype))
	    return (0);
	newsep = pr->pr_dist;
    }
    else newsep = 0;
    if (oldsep < newsep)
	newsep = oldsep;

    /* Queue the edge if it hasn't already moved far enough */
    newx = movingEdge->e_newx + newsep;
    if (newx > impactedEdge->e_newx)
    {
	impactedEdge->e_newx = newx;
	(void) (*plowPropagateProcPtr)(impactedEdge);
    }

    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * prSliverTop --
 * prSliverBot --
 *
 * Avoid introducing slivers due to width or spacing rule violations.
 * For each design rule in 'rules', we perform two passes.  The first
 * pass determines which slivers must move, and how far they must move.
 * The second pass actually queues the edges to be moved.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May add an edge to the queue of edges to be processed.
 *
 * ----------------------------------------------------------------------------
 */

Void
prSliverTop(edge, rules)
    register Edge *edge;
    PlowRule *rules;
{
    register PlowRule *pr;
    struct applyRule ar;
    Point startPoint;

    /*
     * The rules in the list 'rules' only determine the outline that will be
     * traced, not the rules that will be applied to detect slivers.  Hence
     * the halo will depend on edge->e_ltype and the "sliver type" that will
     * be stored in ar.ar_type.  Since we don't know ar.ar_type at the start,
     * we play conservative and just use the maximum halo size.
     */
    if (plowMaxDist[edge->e_ltype] == 0)
	return;
    ar.ar_clip.p_x = edge->e_newx;
    ar.ar_clip.p_y = edge->e_ytop + plowMaxDist[edge->e_ltype];
    startPoint.p_x = edge->e_x;
    startPoint.p_y = edge->e_ytop;
    ar.ar_moving = edge;
    /* We don't use ar.ar_rule */

    for (pr = rules; pr; pr = pr->pr_next)
    {
	/*
	 * Pass 1.
	 * Find out how far slivers will have to move to the right.
	 * After the call to plowSrOutline, ar.ar_mustmove is set to
	 * the distance rightward we will have to move each sliver.
	 */
	ar.ar_slivtype = (TileType) -1;
	ar.ar_lastx = ar.ar_mustmove = edge->e_x;
	plowSrOutline(edge->e_pNum, &startPoint, pr->pr_ltypes, GEO_NORTH,
		GMASK_NORTH|GMASK_EAST|GMASK_SOUTH,
		plowSliverTopExtent, (ClientData) &ar);

	/*
	 * Pass 2.
	 * No work is required if there aren't any potential slivers.
	 * If we must do any work, move each sliver as far rightward
	 * as ar.ar_mustmove.  This second pass doesn't use ar_slivtype,
	 * ar_lastx, or ar_clip.
	 */
	if (ar.ar_mustmove > edge->e_x)
	    plowSrOutline(edge->e_pNum, &startPoint, pr->pr_ltypes, GEO_NORTH,
		    GMASK_SOUTH|GMASK_NORTH,
		    plowSliverTopMove, (ClientData) &ar);
    }
}

Void
prSliverBot(edge, rules)
    register Edge *edge;
    PlowRule *rules;
{
    TileTypeBitMask insideTypes;
    register PlowRule *pr;
    struct applyRule ar;
    Point startPoint;

    /*
     * The rules in the list 'rules' only determine the outline that will be
     * traced, not the rules that will be applied to detect slivers.  Hence
     * the halo will depend on edge->e_ltype and the "sliver type" that will
     * be stored in ar.ar_type.  Since we don't know ar.ar_type at the start,
     * we play conservative and just use the maximum halo size.
     */
    if (plowMaxDist[edge->e_ltype] == 0)
	return;
    ar.ar_clip.p_x = edge->e_newx;
    ar.ar_clip.p_y = edge->e_ybot - plowMaxDist[edge->e_ltype];
    startPoint.p_x = edge->e_x;
    startPoint.p_y = edge->e_ybot;
    ar.ar_moving = edge;
    /* We don't use ar.ar_rule */

    for (pr = rules; pr; pr = pr->pr_next)
    {
	/*
	 * Pass 1.
	 * Find out how far slivers will have to move to the right.
	 * After the call to plowSrOutline, ar.ar_mustmove is set to
	 * the distance rightward we will have to move each sliver.
	 */
	ar.ar_slivtype = (TileType) -1;
	ar.ar_lastx = ar.ar_mustmove = edge->e_x;
	TTMaskCom2(&insideTypes, &pr->pr_ltypes);
	plowSrOutline(edge->e_pNum, &startPoint, insideTypes, GEO_SOUTH,
		GMASK_NORTH|GMASK_EAST|GMASK_SOUTH,
		plowSliverBotExtent, (ClientData) &ar);

	/*
	 * Pass 2.
	 * No work is required if there aren't any potential slivers.
	 * If we must do any work, move each sliver as far rightward
	 * as ar.ar_mustmove.  This second pass doesn't use ar_slivtype,
	 * ar_lastx, or ar_clip.
	 */
	if (ar.ar_mustmove > edge->e_x)
	    plowSrOutline(edge->e_pNum, &startPoint, insideTypes, GEO_SOUTH,
		    GMASK_SOUTH|GMASK_NORTH,
		    plowSliverBotMove, (ClientData) &ar);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowSliverTopMove --
 * plowSliverBotMove --
 *
 * After a first pass through plowSrOutline using plowSliverTopExtent()
 * or plowSliverBotExtent() has filled in ar->ar_mustmove, we get called
 * by a second pass of plowSrOutline to eliminate slivers by moving
 * vertical edges in the outline.
 *
 * All slivers must be moved as far as ar->ar_mustmove.  If we see a vertical
 * edge as far as or farther right than ar->ar_mustmove, we are done.
 * (We know from plowSliverTopExtent() or plowSliverBotExtent() that the final
 * X coordinates of all vertical edges passed to this procedure are
 * monotonically nondecreasing up to ar->ar_mustmove).
 *
 * If the vertical edge is going GEO_SOUTH instead of GEO_NORTH (in the
 * case of plowSliverTopMove), or going GEO_NORTH instead of GEO_SOUTH
 * (in the case of plowSliverBotMove), this is also a stopping condition.
 *
 * Results:
 *	Returns 1 to stop plowSrOutline from following the outline
 *	any more, or 0 to continue.
 *
 * Side effects:
 *	May add an edge to the queue of edges to be processed.
 *
 * ----------------------------------------------------------------------------
 */

int
plowSliverTopMove(outline, ar)
    register Outline *outline;		/* Segment of outline being followed */
    register struct applyRule *ar;
{
    int howfar = ar->ar_moving->e_newx - ar->ar_moving->e_x;
    Edge edge;

    /* Done if we turned south or if this edge is far enough to the right */
    if (outline->o_currentDir == GEO_SOUTH
	    || TRAILING(outline->o_outside) >= ar->ar_mustmove)
	return (1);

    /* Queue the edge to be moved */
    edge.e_rect = outline->o_rect;
    edge.e_newx = ar->ar_mustmove;
    edge.e_ltype = TiGetTypeExact(outline->o_inside);
    edge.e_rtype = TiGetTypeExact(outline->o_outside);
    if (TTMaskHasType(&PlowFixedTypes, edge.e_rtype)
	    && edge.e_newx > edge.e_x + howfar)
	edge.e_newx = edge.e_x + howfar;
    edge.e_pNum = outline->o_pNum;
    edge.e_use = (CellUse *) NULL;
    edge.e_flags = 0;
    (void) (*plowPropagateProcPtr)(&edge);

    /* Keep going */
    return (0);
}

int
plowSliverBotMove(outline, ar)
    register Outline *outline;		/* Segment of outline being followed.
					 * The sense of "inside" and "outside"
					 * is reversed from that when handling
					 * the top half of the penumbra.
					 */
    register struct applyRule *ar;
{
    int howfar = ar->ar_moving->e_newx - ar->ar_moving->e_x;
    Edge edge;

    /* Done if we turned north or if this edge is far enough to the right */
    if (outline->o_currentDir == GEO_NORTH
	    || TRAILING(outline->o_inside) >= ar->ar_mustmove)
	return (1);

    /* Queue the edge to be moved */
    edge.e_rect = outline->o_rect;
    edge.e_newx = ar->ar_mustmove;
    edge.e_ltype = TiGetTypeExact(outline->o_outside);
    edge.e_rtype = TiGetTypeExact(outline->o_inside);
    if (TTMaskHasType(&PlowFixedTypes, edge.e_rtype)
	    && edge.e_newx > edge.e_x + howfar)
	edge.e_newx = edge.e_x + howfar;
    edge.e_pNum = outline->o_pNum;
    edge.e_use = (CellUse *) NULL;
    edge.e_flags = 0;
    (void) (*plowPropagateProcPtr)(&edge);

    /* Keep going */
    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowSliverTopExtent --
 * plowSliverBotExtent --
 *
 * Called by plowSrOutline() to follow the upper/lower penumbra's
 * outline(s), and see how much of that outline must be moved to
 * avoid introducing slivers.
 *
 * Results:
 *	Returns 1 to stop plowSrOutline from following the outline
 *	any more, or 0 to continue.
 *
 * Side effects:
 *	Modifies the following fields of the applyRule struct pointed
 *	to by 'ar':
 *
 *		ar_lastx	-- continuously updated to the rightmost
 *				   final X coordinate of any vertical
 *				   edge.  When a vertical edge is seen
 *				   with a final X coordinate less than
 *				   ar_lastx, we know we're done.
 *		ar_mustmove	-- new X to which each sliver must be moved.
 *		ar_slivtype	-- material forming the sliver itself.
 *
 * ----------------------------------------------------------------------------
 */

int
plowSliverTopExtent(outline, ar)
    register Outline *outline;		/* Segment of outline being followed */
    register struct applyRule *ar;
{
    register Edge *movingEdge = ar->ar_moving;
    int newx, xmove, ret = 0;

    /*
     * The direction in which we are following an outline
     * segment generally determines how it is processed.
     *
     *	If heading south, we are definitely done.
     *	If heading north, we check (by comparing with ar->ar_lastx)
     *	    to make sure the outline's X coordinate is monotonically
     *	    nondecreating.
     *	If heading east, we check to see if this segment of the outline
     *	    causes a design-rule violation (i.e, if there is a sliver
     *	    between movingEdge->e_ltype and outline->o_inside).
     *
     * A few exceptions arise because we are considering final instead
     * of initial coordinates.  These are both when heading north.  See
     * the code for details.
     */
    switch (outline->o_currentDir)
    {
	case GEO_SOUTH:
	    /* Done if we turn back down */
	    return (1);

	case GEO_NORTH:
	    /* Done if we turned left in final coordinates */
	    if (TRAILING(outline->o_outside) < ar->ar_lastx)
		return (1);

	    /* Almost done if we exit the right or top of the clip area */
	    newx = TRAILING(outline->o_outside);
	    if (newx >= ar->ar_clip.p_x
		    || outline->o_rect.r_ytop >= ar->ar_clip.p_y)
		ret = 1;

	    /* Set the type to be used in applying the design rules */
	    if (outline->o_rect.r_ybot == movingEdge->e_ytop)
		ar->ar_slivtype = TiGetTypeExact(outline->o_outside);

	    /*
	     * If this is not a special case where we have to check for
	     * a sliver, we're done.
	     *
	     * Normally, slivers are only checked for when going east.
	     * There are two exceptions, both because we are looking at
	     * final coordinates.
	     *
	     *	1. If we were going west and then turned north, check
	     *	   for a sliver with the bottom of the inside material.
	     *
	     *	initial | ------------>	| final
	     *		+-------+
	     *			|
	     *
	     *	2. If we were going north and kept going, and this segment
	     *	   is further right than the last one (in final coordinates),
	     *	   check for a sliver with the bottom of the inside material.
	     *
	     *	initial | ------------>	| final
	     *		|
	     */
	    if (outline->o_prevDir == GEO_WEST
		    || (outline->o_prevDir == GEO_NORTH && newx > ar->ar_lastx))
	    {
		ar->ar_lastx = newx;
		xmove = MIN(newx, ar->ar_clip.p_x);
		break;
	    }

	    /* We know newx >= ar->ar_lastx, so update the latter */
	    ar->ar_lastx = newx;
	    return (ret);

	case GEO_EAST:
	    /* Almost done if we leave the clipping area */
	    if (outline->o_rect.r_xtop >= ar->ar_clip.p_x)
		ret = 1;

	    /*
	     * Return if we haven't yet processed a vertical segment of
	     * the outline (i.e, no potential slivers have been seen yet).
	     */
	    if (ar->ar_slivtype == (TileType) -1)
		return (ret);

	    /* How far the penumbra LHS will have to move to avoid a sliver */
	    xmove = ar->ar_clip.p_x;
	    if (outline->o_nextDir == GEO_NORTH
		    && TRAILING(outline->o_nextOut) < xmove)
		xmove = TRAILING(outline->o_nextOut);
	    break;
    }

    /* Apply the plowing rules to see if this is a sliver */
    if (plowSliverApplyRules(ar, TiGetTypeExact(outline->o_inside),
		outline->o_rect.r_ybot - movingEdge->e_ytop))
	ar->ar_mustmove = xmove;
    return (ret);
}

int
plowSliverBotExtent(outline, ar)
    register Outline *outline;		/* Segment of outline being followed */
    register struct applyRule *ar;
{
    register Edge *movingEdge = ar->ar_moving;
    int newx, xmove, ret = 0;

    /*
     * The direction in which we are following an outline
     * segment generally determines how it is processed.
     *
     *	If heading north, we are definitely done.
     *	If heading south, we check (by comparing with ar->ar_lastx)
     *	    to make sure the outline's X coordinate is monotonically
     *	    nondecreating.
     *	If heading east, we check to see if this segment of the outline
     *	    causes a design-rule violation (i.e, if there is a sliver
     *	    between movingEdge->e_ltype and outline->o_outside).
     *
     * A few exceptions arise because we are considering final instead
     * of initial coordinates.  These are both when heading south.  See
     * the code for details.
     */
    switch (outline->o_currentDir)
    {
	case GEO_NORTH:
	    /* Done if we turn back up */
	    return (1);

	case GEO_SOUTH:
	    /* Done if we turned left in final coordinates */
	    if (TRAILING(outline->o_inside) < ar->ar_lastx)
		return (1);

	    /* Almost done if we exit the right or bottom of the clip area */
	    newx = TRAILING(outline->o_inside);
	    if (newx >= ar->ar_clip.p_x
		    || outline->o_rect.r_ybot <= ar->ar_clip.p_y)
		ret = 1;

	    /* Set the type to be used in applying the design rules */
	    if (outline->o_rect.r_ytop == movingEdge->e_ybot)
		ar->ar_slivtype = TiGetTypeExact(outline->o_inside);

	    /*
	     * If this is not a special case where we have to check for
	     * a sliver, we're done.
	     *
	     * Normally, slivers are only checked for when going east.
	     * There are two exceptions, both because we are looking at
	     * final coordinates.
	     *
	     *	1. If we were going west and then turned south, check
	     *	   for a sliver with the top of the outside material.
	     *
	     *			|
	     *		+-------+
	     *	initial | ------------>	| final
	     *
	     *	2. If we were going south and kept going, and this segment
	     *	   is further right than the last one (in final coordinates),
	     *	   check for a sliver with the top of the outside material.
	     *
	     *		|
	     *	initial | ------------>	| final
	     */
	    if (outline->o_prevDir == GEO_WEST
		    || (outline->o_prevDir == GEO_SOUTH && newx > ar->ar_lastx))
	    {
		ar->ar_lastx = newx;
		xmove = MIN(newx, ar->ar_clip.p_x);
		break;
	    }

	    /* We know newx >= ar->ar_lastx, so update the latter */
	    ar->ar_lastx = newx;
	    return (ret);

	case GEO_EAST:
	    /* Almost done if we leave the clipping area */
	    if (outline->o_rect.r_xtop >= ar->ar_clip.p_x)
		ret = 1;

	    /*
	     * Return if we haven't yet processed a vertical segment of
	     * the outline (i.e, no potential slivers have been seen yet).
	     */
	    if (ar->ar_slivtype == (TileType) -1)
		return (ret);

	    /* How far the penumbra LHS will have to move to avoid a sliver */
	    xmove = ar->ar_clip.p_x;
	    if (outline->o_nextDir == GEO_SOUTH
		    && TRAILING(outline->o_nextIn) < xmove)
		xmove = TRAILING(outline->o_nextIn);
	    break;
    }

    /* Apply the plowing rules to see if this is a sliver */
    if (plowSliverApplyRules(ar, TiGetTypeExact(outline->o_outside),
		movingEdge->e_ybot - outline->o_rect.r_ytop))
	ar->ar_mustmove = xmove;
    return (ret);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowSliverApplyRules --
 *
 * Determine whether a sliver exists that must be moved.  The configuration
 * we are evaluating is:
 *
 *
 *	   far
 *	--------
 *		  ^
 *		  |
 *		  | farDist
 *		  |
 *	    s	  v
 *	--------
 *	  near
 *
 * where s = ar->ar_slivtype and near = ar->ar_moving->e_ltype.
 * We apply all the plowing rules triggered by the near|s edge
 * (both width and spacing rules).
 *
 * Results:
 *	TRUE if the sliver must move, FALSE if not.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

bool
plowSliverApplyRules(ar, far, farDist)
    register struct applyRule *ar;
    TileType far;
    int farDist;
{
    TileType near = ar->ar_moving->e_ltype;
    register PlowRule *pr;

    for (pr = plowWidthRulesTbl[near][ar->ar_slivtype]; pr; pr = pr->pr_next)
	if (pr->pr_dist > farDist && !TTMaskHasType(&pr->pr_oktypes, far))
	    return (TRUE);

    for (pr = plowSpacingRulesTbl[near][ar->ar_slivtype]; pr; pr = pr->pr_next)
	if (pr->pr_dist > farDist && !TTMaskHasType(&pr->pr_oktypes, far))
	    return (TRUE);
    return (FALSE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowApplyRule --
 *
 * Add an edge ('impactedEdge') found by shadow search to the edge queue.
 * Normally, this edge will be added at a new position equal to the new
 * position of the original moving edge (ar->ar_moving), plus the distance
 * associated with the design rule (ar->ar_rule->pr_dist).  However, if
 * the impacted edge were already closer than ar->ar_rule->pr_dist to the
 * moving edge (i.e, there was a design-rule violation in the original
 * layout), we use the original separation instead of the minimum separation.
 *
 * If the impacted edge has already moved far enough (impactedEdge->e_newx
 * is far enough to the right), we don't add it.
 *
 * If ar->ar_rule is NULL, we assume a distance of zero.
 *
 * Results:
 *	Always returns 0.
 *
 * Side effects:
 *	May add an edge to the queue of edges to be processed.
 *
 * ----------------------------------------------------------------------------
 */

int
plowApplyRule(impactedEdge, ar)
    register Edge *impactedEdge;	/* Edge found by shadow search */
    register struct applyRule *ar;	/* Edge causing the shadow search, and
					 * the design rule to apply.
					 */
{
    register Edge *movingEdge = ar->ar_moving;
    int newsep, oldsep, newx;

    oldsep = impactedEdge->e_x - movingEdge->e_x;
    newsep = ar->ar_rule ? ar->ar_rule->pr_dist : 0;
    if (oldsep < newsep)
	newsep = oldsep;

    /* Queue the edge if it hasn't already moved far enough */
    newx = movingEdge->e_newx + newsep;
    if (newx > impactedEdge->e_newx)
    {
	impactedEdge->e_newx = newx;
	(void) (*plowPropagateProcPtr)(impactedEdge);
    }

    return (0);
}
