/*
 * DBtechpaint.c --
 *
 * Management of composition rules and the paint/erase tables.
 *
 *     *********************************************************************
 *     * 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/database/DBtpaint.c,v 1.2 2010/06/08 19:16:42 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <ctype.h>
#include <string.h>	/* for memset(), memcpy() */

#include "utils/magic.h"
#include "utils/geometry.h"
#include "utils/utils.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "database/databaseInt.h"
#include "utils/tech.h"
#include "textio/textio.h"

    /* Painting and erasing tables */
PaintResultType DBPaintResultTbl[NP][NT][NT];
PaintResultType DBEraseResultTbl[NP][NT][NT];
PaintResultType DBWriteResultTbl[NT][NT];
PaintResultType DBSpecialResultTbl[NT];

PlaneMask DBTypePaintPlanesTbl[NT];
PlaneMask DBTypeErasePlanesTbl[NT];

/* ----------------- Data local to tech file processing --------------- */

/*
 * Tables telling which rules are default, and which have come
 * from user-specified rules.  The bit is CLEAR if the type is
 * a default type.
 */
TileTypeBitMask dbNotDefaultEraseTbl[NT];
TileTypeBitMask dbNotDefaultPaintTbl[NT];

/* --------------------- Data local to this file ---------------------- */

int dbNumSavedRules = 0;
Rule dbSavedRules[NT];

/* Forward declarations */

extern void dbTechBitTypeInit();

bool dbTechAddPaintErase();
bool dbTechSaveCompose();

/*
 * ----------------------------------------------------------------------------
 *
 * DBTechInitCompose --
 *
 * Initialize the painting and erasing rules prior to processing
 * the "compose" section.  The rules for builtin types are computed
 * here, as well as the default rules for all other types.  This
 * procedure must be called after the "types" and "contacts" sections
 * have been read, since we need to know about all existing tile types.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the paint and erase tables.
 *
 * ----------------------------------------------------------------------------
 */

void
DBTechInitCompose()
{
    TileType q, s, t, r;
    int ps;
    PaintResultType *stype, *dtype;
    TileTypeBitMask *ttype;

    /* Default painting rules for error types */

    static TileType errorBitToType[] =
    {
	TT_SPACE,	/* 0 */		TT_ERROR_P,	/* 1 */
	TT_ERROR_S,	/* 2 */		TT_ERROR_PS,	/* 3 */
    };

    /* Painting and erasing are no-ops for undefined tile types */

    /* The following code is the FAST version using memcpy().	*/
    /* See below for the actual slow loops.			*/

    stype = dtype = &(DBEraseResultTbl[0][0][0]);
    for (ps = 0; ps < TT_MAXTYPES; ps++)
	*dtype++ = (PaintResultType)ps;
    for (ps = 1; ps < PL_MAXTYPES * TT_MAXTYPES; ps++)
    {
	memcpy((void *)dtype, (void *)stype, (size_t)TT_MAXTYPES
		* sizeof(PaintResultType));
	dtype += TT_MAXTYPES;
    }

    /* Fast copy the entire erase table to the paint table memory */
    dtype = &(DBPaintResultTbl[0][0][0]);
    memcpy((void *)dtype, (void *)stype, (size_t)(TT_MAXTYPES
		* TT_MAXTYPES * PL_MAXTYPES * sizeof(PaintResultType)));

    /* The following code is dreadfully slow, but I'm leaving it */
    /* in as a comment because it's easier to read.  The code	 */
    /* above uses memory copying tricks to speed up the process. */
    /*

    for (pNum = 0; pNum < PL_MAXTYPES; pNum++)
    {
	for (s = 0; s < TT_MAXTYPES; s++)
	{
	    for (t = 0; t < TT_MAXTYPES; t++)
	    {
		/- Paint and erase are no-ops -/
		dbSetEraseEntry(s, t, pNum, s);
		dbSetPaintEntry(s, t, pNum, s);
	    }
	}
    }

    for (s = 0; s < TT_MAXTYPES; s++)
    {
	for (t = 0; t < TT_MAXTYPES; t++)
	{
	    /- Write overwrites existing contents -/
	    dbSetWriteEntry(s, t, t);
	}
    }
    */

#if TT_MAXTYPES <= 256
    /* For single-byte values, memset() is fastest. */

    dtype = &(DBWriteResultTbl[0][0]);
    for (q = 0; q < TT_MAXTYPES; q++)
    {
	memset((void *)dtype, (int)q, (size_t)TT_MAXTYPES);
	dtype += TT_MAXTYPES;
    }

#else
    /* This is the slow loop, but it still faster than using	*/
    /* macro dbSetWriteEntry().					*/

    dtype = &(DBWriteResultTbl[0][0]);
    for (t = 0; t < TT_MAXTYPES; t++)
	for (s = 0; s < TT_MAXTYPES; s++)
	    *dtype++ = t;

#endif

    /* All painting and erasing rules are default initially */

    /* This is also faster than the loop below. */

    ttype = &(dbNotDefaultEraseTbl[0]);
    for (s = 0; s < DBNumTypes; s++) *ttype++ = DBZeroTypeBits;
    ttype = &(dbNotDefaultPaintTbl[0]);
    for (s = 0; s < DBNumTypes; s++) *ttype++ = DBZeroTypeBits;

    /*
    for (s = 0; s < DBNumTypes; s++)
    {
	dbNotDefaultEraseTbl[s] = DBZeroTypeBits;
	dbNotDefaultPaintTbl[s] = DBZeroTypeBits;
    }
    */

    /*
     *	For each type t:
     *	    erase(t, t, plane(t)) -> SPACE
     *
     *	For each type s, t:
     *	    paint(s, t, plane(t)) -> t
     *	    paint(s, t, ~plane(t)) -> s
     */

    for (s = 0; s < DBNumTypes; s++)
    {
	if ((ps = DBPlane(s)) > 0)
	{
	    for (t = 0; t < DBNumTypes; t++)
	    {
		if (DBPlane(t) > 0)
		{
		    r = (ps == DBPlane(t)) ? t : s;
		    dbSetEraseEntry(s, t, ps, s);
		    dbSetPaintEntry(s, t, ps, r);
		}
	    }

	    /* Everything can be erased to space on its home plane */
	    dbSetEraseEntry(s, s, ps, TT_SPACE);

	    /* Everything paints over space on its home plane */
	    dbSetPaintEntry(TT_SPACE, s, ps, s);
	}
    }

    /*
     * Special handling for check tile and error tile combinations.
     */
#define	PCHK	PL_DRC_CHECK
#define	PERR	PL_DRC_ERROR
#define	tblsize(t)	( (sizeof (t)) / (sizeof (t[0])) )
    dbTechBitTypeInit(errorBitToType, tblsize(errorBitToType), PERR, FALSE);
#undef	tblsize

    /*
     * Paint results are funny for check plane because
     * CHECKPAINT+CHECKSUBCELL = CHECKPAINT
     */
    dbSetPaintEntry(TT_SPACE, TT_CHECKPAINT, PCHK, TT_CHECKPAINT);
    dbSetPaintEntry(TT_SPACE, TT_CHECKSUBCELL, PCHK, TT_CHECKSUBCELL);
    dbSetPaintEntry(TT_CHECKPAINT, TT_CHECKSUBCELL, PCHK, TT_CHECKPAINT);
    dbSetPaintEntry(TT_CHECKSUBCELL, TT_CHECKPAINT, PCHK, TT_CHECKPAINT);
#undef	PCHK
#undef	PERR

    /* Added 5/27/10:  Special table used for painting non-Manhattan	*/
    /* tiles.  Uses TT_CHECKSUBCELL because that type does not exist on	*/
    /* any paintable plane, and the checkpaint plane does not use non-	*/
    /* manhattan tiles.							*/

    for (s = 0; s < DBNumTypes; s++) DBSpecialResultTbl[s] = TT_CHECKSUBCELL;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechBitTypeInit --
 *
 * Handle initialization of the paint and erase result tables for a
 * set of ln2(n) primary types with n distinct mutual overlap types.
 * The table bitToType points to a table containing n TileTypes
 * (the overlap types) with the property that
 *
 *	bitToType[i] and bitToType[j] combine to yield bitToType[i | j]
 *
 * Also (unless composeFlag is set) erasing bitToType[j] from bitToType[i]
 * gives bitToType[i & (~j)],
 * i.e., it clears all of the j-type material out of the i-type material.
 * The bitToType[k] for which k's binary representation has only a single
 * bit set in it are the "primary" types.
 *
 * If composeFlag is set, the above is modified slightly to be analagous
 * to compose rules, specifically, erase rules for nonprimary types are
 * the default rules, i.e. they only erase precisely themselves.  This
 * makes ":erase *-primary" work in the expected way.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See above.
 *
 * ----------------------------------------------------------------------------
 */

void
dbTechBitTypeInit(bitToType, n, pNum, composeFlag)
    TileType *bitToType;
    int n, pNum;
    bool composeFlag;
{
    int i, j;
    TileType have, type;

    for (i = 0; i < n; i++)
    {
	have = bitToType[i];
	for (j = 0; j < n; j++)
	{
	    type = bitToType[j];
	    dbSetPaintEntry(have, type, pNum, bitToType[i | j]);
	    if(!composeFlag || dbIsPrimary(j))
	    {
	        dbSetEraseEntry(have, type, pNum, bitToType[i & (~j)]);
	    }
	}
    }
}

/* Returns nonzero if exactly one bit set */

int
dbIsPrimary(n)
    int n;
{
    int bitCount;

    for(bitCount=0; n>0; n=n>>1)
    {
        if(n&1)
        {
	    bitCount++;
        }
    }

    return (bitCount==1);
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBTechAddCompose --
 *
 * Process a single compose/erase rule.  If the type being described is
 * a contact, save the rule and defer processing it until the end of this
 * section, because we need to know the behavior of all non-contact types
 * that might be residues before processing a contact composition rule.
 * Rules for non-contact types are processed here.
 *
 * Results:
 *	TRUE if successful, FALSE on error.
 *
 * Side effects:
 *	Modifies the paint/erase tables if the type being described
 *	is not a contact; otherwise, appends a rule to the list of
 *	contact erase/compose rules for later processing.  Marks the
 *	paint/erase table entries affected to show that they contain
 *	user-specified rules instead of the default ones, so we don't
 *	override them later.
 *
 * ----------------------------------------------------------------------------
 */

    /*ARGSUSED*/
bool
DBTechAddCompose(sectionName, argc, argv)
    char *sectionName;
    int argc;
    char *argv[];
{
    TileType type, r, s;
    int pNum, ruleType, i;
    static const char * const ruleNames[] =
	{ "compose", "decompose", "paint", "erase", 0 };
    static const int ruleTypes[] =
	{ RULE_COMPOSE, RULE_DECOMPOSE, RULE_PAINT, RULE_ERASE };

    if (argc < 4)
    {
	TechError("Line must contain at least ruletype, result + pair\n");
	return FALSE;
    }

    /* Look up and skip over type of rule */
    i = Lookup(*argv, ruleNames);
    if (i < 0)
    {
	TechError("%s rule type %s.  Must be one of:\n\t",
		i == -1 ? "Ambiguous" : "Unknown", *argv);
	for (i = 0; ruleNames[i]; i++)
	    TxError("\"%s\" ", ruleNames[i]);
	TxError("\n");
	return FALSE;
    }
    ruleType = ruleTypes[i];
    argv++, argc--;

    /* Paint or erase rules are processed specially */
    switch (ruleType)
    {
	case RULE_PAINT:
	case RULE_ERASE:
	    return (dbTechAddPaintErase(ruleType, sectionName, argc, argv));
    }

    /* Compose or decompose rule: find result type and then skip over it */
    if ((type = DBTechNoisyNameType(*argv)) < 0)
	return FALSE;
    argv++, argc--;
    if (argc & 01)
    {
	TechError("Types on RHS of rule must be in pairs\n");
	return FALSE;
    }

    /* Compose/decompose rules for contacts are saved away */
    if (IsContact(type))
	return dbTechSaveCompose(ruleType, type, argc, argv);

    /* Rules for non-contacts are processed here */
    for ( ; argc > 0; argc -= 2, argv += 2)
    {
	if ((r = DBTechNoisyNameType(argv[0])) < 0
		|| (s = DBTechNoisyNameType(argv[1])) < 0)
	    return FALSE;

	if (IsContact(r) || IsContact(s))
	{
	    TechError("Can't have contact layers on RHS of non-contact rule\n");
	    return FALSE;
	}

	pNum = DBPlane(r);
	switch (ruleType)
	{
	    case RULE_COMPOSE:
		dbSetPaintEntry(r, s, pNum, type);
		dbSetPaintEntry(s, r, pNum, type);
		TTMaskSetType(&dbNotDefaultPaintTbl[r], s);
		TTMaskSetType(&dbNotDefaultPaintTbl[s], r);
		/* Fall through to */
	    case RULE_DECOMPOSE:
		dbSetPaintEntry(type, r, pNum, type);
		dbSetPaintEntry(type, s, pNum, type);
		dbSetEraseEntry(type, r, pNum, s);
		dbSetEraseEntry(type, s, pNum, r);
		TTMaskSetType(&dbNotDefaultPaintTbl[type], r);
		TTMaskSetType(&dbNotDefaultPaintTbl[type], s);
		TTMaskSetType(&dbNotDefaultEraseTbl[type], r);
		TTMaskSetType(&dbNotDefaultEraseTbl[type], s);
		break;
	}
    }

    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechSaveCompose --
 *
 * Save a compose rule for a contact 't' in the table dbSavedRules.
 * Check to make sure the rule is legal.
 *
 * Results:
 *	Returns TRUE if successful, FALSE on error.
 *
 * Side effects:
 *	Updates dbSavedRules[] and increments dbNumSavedRules.
 *
 * ----------------------------------------------------------------------------
 */

bool
dbTechSaveCompose(ruleType, t, argc, argv)
    int ruleType;
    TileType t;
    int argc;
    char *argv[];
{
    TileType r, s;
    Rule *rp;

    rp = &dbSavedRules[dbNumSavedRules++];
    rp->r_ruleType = ruleType;
    rp->r_result = t;
    rp->r_npairs = 0;
    for ( ; argc > 0; argc -= 2, argv += 2)
    {
	r = DBTechNoisyNameType(argv[0]);
	s = DBTechNoisyNameType(argv[1]);
	if (r < 0 || s < 0)
	    return FALSE;

	/* At most one of r and s may be a contact */
	if (IsContact(r) && IsContact(s))
	{
	    TechError("Only one type in each pair may be a contact\n");
	    return FALSE;
	}

	/*
	 * The planes comprising 't' must be a superset of the
	 * planes comprising 'r' and the planes comprising 's'.
	 */
	if (((LayerPlaneMask(r) | LayerPlaneMask(s)) & ~LayerPlaneMask(t)) != 0)
	{
	    TechError("Component planes are a superset of result planes\n");
	    return FALSE;
	}

	if (ruleType == RULE_COMPOSE)
	{
	    /* Types r and s can't appear on planes outside of t's */
	    if ((LayerPlaneMask(r) | LayerPlaneMask(s)) != LayerPlaneMask(t))
	    {
		TechError("Union of pair planes must = result planes\n");
		return FALSE;
	    }

	    /* The following restriction has been lifted due to	*/
	    /* the recursive plane painting method added to	*/
	    /* routine DBPaint().	(Tim, 5/11/04)		*/

//	    if (!dbTechCheckImages(t, r, s) || !dbTechCheckImages(t, s, r))
//		return FALSE;
	}

	rp->r_pairs[rp->r_npairs].rp_a = r;
	rp->r_pairs[rp->r_npairs].rp_b = s;
	rp->r_npairs++;
    }

    return TRUE;
}


#if 0	/* deprecated function (5/11/04) */
/*
 * ----------------------------------------------------------------------------
 *
 * dbTechCheckImages --
 *
 * When processing a compose rule for 't' with RHS components
 * 'r' and 's', check to be sure that the images of 't' on
 * those planes present in 'r' but not in 's' are identical to
 * the images of 'r' on those planes.  This is necessary in order
 * that the result on these planes not depend on types present on
 * other planes.
 *
 * Results:
 *	Returns TRUE if successful, FALSE on error.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

bool
dbTechCheckImages(t, r, s)
    TileType t;			/* Type that is composed */
    TileType r;	/* First constituent */
    TileType s;			/* Second constituent */
{
    int pNum;
    PlaneMask pMask;

    if (pMask = (LayerPlaneMask(r) & ~LayerPlaneMask(s)))
    {
	for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++)
	    if (PlaneMaskHasPlane(pMask, pNum) && (t != r))
	    {
		TechError("Result image on plane %s must be the same "
			"as image of %s on plane %s\n",
			DBPlaneLongName(pNum),
			DBTypeLongName(r),
			DBPlaneLongName(pNum));
		return FALSE;
	    }
    }

    return TRUE;
}
#endif	/* 0 */


/*
 * ----------------------------------------------------------------------------
 *
 * dbTechAddPaintErase --
 *
 * Add a new entry to the paint or erase table.
 * The semantics is that painting a tile of type1 with paint type2
 * yields a tile of typeres.
 *
 * Results:
 *	Returns TRUE if successful, FALSE on error.
 *
 * Side effects:
 *	Updates the database technology variables.
 *
 * ----------------------------------------------------------------------------
 */

bool
dbTechAddPaintErase(type, sectionName, argc, argv)
    int type;
    char *sectionName;
    int argc;
    char *argv[];
{
    int pNum;
    PlaneMask pMask, rMask;
    TileType t1, t2, tres;
    TileTypeBitMask tMask;

    if (argc < 3)
    {
	TechError("Line must contain at least 3 types\n");
	return FALSE;
    }

    if ((t1 = DBTechNoisyNameType(argv[0])) < 0) return FALSE;
    if ((t2 = DBTechNoisyNameType(argv[1])) < 0) return FALSE;

    /* Modified 9/22/2020 to allow multiple types to paint, for example	*/
    /* to replace a contact type with types on both residue planes.	*/

    rMask = DBTechNoisyNameMask(argv[2], &tMask);
    if (TTMaskIsZero(&tMask)) return FALSE;

    if (argc == 3)
    {
	if (t1 == TT_SPACE)
	{
	    TechError("<%s, %s, %s>:\n"
			"Must specify plane in paint table for "
			"painting space\n",
			argv[0], argv[1], argv[2]);
	    return FALSE;
	}
	else
	   pMask = LayerPlaneMask(t1);
    }
    else
    {
	if ((pNum = DBTechNoisyNamePlane(argv[3])) < 0) return FALSE;
	else
	    pMask = PlaneNumToMaskBit(pNum);
    }

    pMask &= ~rMask;

    /* 10/30/2020:  Changed from DBNumTypes to DBNumUserLayers,	    */
    /* because DBTechNoisyNameMask() was modified to add stacking   */
    /* contact types, and it is not correct for tMask to have more  */
    /* than one type in the mask that share the same plane.	    */
    /* NOTE:  Paint rules for stacked contacts probably have to be  */
    /* handled too, but in a separate way.			    */
    
    for (tres = 0; tres < DBNumUserLayers; tres++)
    {
	if (TTMaskHasType(&tMask, tres))
	{
	    if (type == RULE_PAINT)
	    {
		/* Apply to all planes of rMask. */

		for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++)
		    if (PlaneMaskHasPlane(rMask, pNum))
			if (DBTypeOnPlane(tres, pNum))
			    dbSetPaintEntry(t1, t2, pNum, tres);
	    }
	    else	/* (type == RULE_ERASE) */
	    {
		/* Apply to all planes of rMask. */

		for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++)
		    if (PlaneMaskHasPlane(rMask, pNum))
			if (DBTypeOnPlane(tres, pNum))
			    dbSetEraseEntry(t1, t2, pNum, tres);
	    }
	}
    }

    if (type == RULE_PAINT)
    {
	/* For all planes of pMask which are not in rMask, result is space */

	for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++)
	    if (PlaneMaskHasPlane(pMask, pNum))
		    dbSetPaintEntry(t1, t2, pNum, TT_SPACE);
    }
    else	/* (type == RULE_ERASE) */
    {
	/* For all planes of pMask which are not in rMask, result is space */

	for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++)
	    if (PlaneMaskHasPlane(pMask, pNum))
		dbSetEraseEntry(t1, t2, pNum, TT_SPACE);
    }
    TTMaskSetType(&dbNotDefaultPaintTbl[t1], t2);
    return TRUE;
}


/*
 * ----------------------------------------------------------------------------
 *
 * dbTechCheckPaint --
 *
 * DEBUGGING.
 * Check painting and erasing rules to make sure that the result
 * type is legal for the plane being affected.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Prints stuff in the event of an error.
 *
 * ----------------------------------------------------------------------------
 */

void
dbTechCheckPaint(where)
    char *where;	/* If non-null, print this as header */
{
    TileType have, t, result;
    bool printedHeader = FALSE;

    for (have = TT_TECHDEPBASE; have < DBNumTypes; have++)
    {
	for (t = TT_TECHDEPBASE; t < DBNumTypes; t++)
	{
	    result = DBStdPaintEntry(have, t, DBPlane(have));
	    if (result != TT_SPACE && DBPlane(result) != DBPlane(have))
	    {
		if (!printedHeader && where)
		    TxPrintf("\n%s:\n", where), printedHeader = TRUE;
		TxPrintf("%s + %s -> %s\n",
			DBTypeShortName(have), DBTypeShortName(t),
			DBTypeShortName(result));
	    }
	    result = DBStdEraseEntry(have, t, DBPlane(have));
	    if (result != TT_SPACE && DBPlane(result) != DBPlane(have))
	    {
		if (!printedHeader && where)
		    TxPrintf("\n%s:\n", where), printedHeader = TRUE;
		TxPrintf("%s - %s -> %s\n",
			DBTypeShortName(have), DBTypeShortName(t),
			DBTypeShortName(result));
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechPrintPaint --
 *
 * DEBUGGING.
 * Print painting and erasing rules.  If contactsOnly is TRUe, only
 * print those rules involving pairs of contact types.  The argument
 * "where" is printed as a header if it is non-NULL.  If doPaint is
 * TRUE, we print the paint rules, else we print the erase rules.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Prints stuff.
 *
 * ----------------------------------------------------------------------------
 */

void
dbTechPrintPaint(where, doPaint, contactsOnly)
    char *where;	/* If non-null, print this as header */
    bool doPaint;	/* TRUE -> print paint tables, FALSE -> print erase */
    bool contactsOnly;
{
    TileType have, paint, erase, result;
    int plane;
    LayerInfo *lp;

    if (where)
	TxPrintf("\n%s:\n\n", where);

    if (doPaint)
    {
	TxPrintf("PAINTING RULES:\n");
	for (have = TT_TECHDEPBASE; have < DBNumTypes; have++)
	{
	    if (contactsOnly && !IsContact(have)) continue;
	    for (paint = TT_TECHDEPBASE; paint < DBNumTypes; paint++)
	    {
		if (contactsOnly && !IsContact(paint)) continue;
		for (plane = PL_TECHDEPBASE; plane < DBNumPlanes; plane++)
		{
		    lp = &dbLayerInfo[have];
		    if (!PlaneMaskHasPlane(lp->l_pmask, plane))
			continue;

		    result = DBStdPaintEntry(have, paint, plane);
		    if (result != have)
		    {
		        TxPrintf("%s ",
				DBTypeShortName(have));
			if (IsContact(have))
		            TxPrintf("(on %s) ",
					DBPlaneLongName(plane));
		        TxPrintf(" + %s -> %s\n",
				DBTypeShortName(paint),
				DBTypeShortName(result));
		    }
		}
	    }
	}
    }
    else
    {
	TxPrintf("ERASING RULES:\n");
	for (have = TT_TECHDEPBASE; have < DBNumTypes; have++)
	{
	    if (contactsOnly && !IsContact(have)) continue;
	    for (erase = TT_TECHDEPBASE; erase < DBNumTypes; erase++)
	    {
		if (contactsOnly && !IsContact(erase)) continue;
		for (plane = PL_TECHDEPBASE; plane < DBNumPlanes; plane++)
		{
		    lp = &dbLayerInfo[have];
		    if (!PlaneMaskHasPlane(lp->l_pmask, plane))
			continue;

		    result = DBStdEraseEntry(have, erase, plane);
		    if (result != have)
		    {
		        TxPrintf("%s ",
				DBTypeShortName(have));
			if (IsContact(have))
		            TxPrintf("(on %s) ",
					DBPlaneLongName(plane));
		        TxPrintf(" - %s -> %s\n",
				DBTypeShortName(erase),
				DBTypeShortName(result));
		    }
		}
	    }
	}
    }
}
