/* CIFreadcell.c -
 *
 *	This file contains more routines to parse CIF files.  In
 *	particular, it contains the routines to handle cells,
 *	both definitions and calls, and user-defined features
 *	like labels.
 *
 *     *********************************************************************
 *     * 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 const char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/cif/CIFrdcl.c,v 1.5 2010/08/25 17:33:55 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "utils/magic.h"
#include "utils/malloc.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "utils/undo.h"
#include "database/database.h"
#include "cif/CIFint.h"
#include "cif/CIFread.h"
#include "cif/cif.h"
#include "calma/calma.h"
#include "utils/utils.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"
#include "utils/main.h"
#include "drc/drc.h"

/* The following variable is made available to the outside world,
 * and is the cell definition currently being modified.
 */

CellDef *cifReadCellDef;

/*
 * The following hash table is used internally to keep track of
 * of all the cells we've seen definitions for or calls on.
 * The hash table entries contain pointers to cellDefs, and
 * are indexed by CIF cell number.  If the CDAVAILABLE bit is
 * set it means we've read the cell's contents.  If not set, it
 * means that the cell has been called but not yet defined.
 */
HashTable CifCellTable;

/* The following variable is used to save and restore current
 * paint layer information so that we can resume the correct
 * layer after a subcell definition.
 */

Plane *cifOldReadPlane = NULL;

/* The following boolean is TRUE if a subcell definition is being
 * read.  FALSE means we're working on the EditCell.
 */

bool cifSubcellBeingRead;

/* The following two collections of planes are used to hold CIF
 * information while cells are being read in (one set for the
 * outermost, unnamed cell, and one for the current subcell).
 * When a cell is complete, then geometrical operations are
 * performed on the layers and stuff is painted into Magic.
 */

Plane *cifEditCellPlanes[MAXCIFRLAYERS];
Plane *cifSubcellPlanes[MAXCIFRLAYERS];
Plane **cifCurReadPlanes = cifEditCellPlanes;	/* Set of planes currently
						 * in force.
						 */
TileType cifCurLabelType = TT_SPACE;	/* Magic layer on which to put '94'
					 * labels that aren't identified by
					 * type.
					 */

/* Structure used when flattening the CIF hierarchy on read-in */

typedef struct {
    Plane *plane;
    Transform *trans;
} CIFCopyRec;

/* The following variable is used to hold a subcell id between
 * the 91 statement and the (immediately-following?) call statement.
 * The string this points to is dynamically allocated, so it must
 * also be freed explicitly.
 */

char *cifSubcellId = NULL;

/*
 * ----------------------------------------------------------------------------
 *
 * CIFReadCellInit --
 *
 * 	This procedure initializes the data structures in this
 *	module just prior to reading a CIF file.
 *
 *	If ptrkeys is 0, the keys used in this hash table will
 *	be strings; if it is 1, the keys will be CIF numbers.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The cell hash table is initialized, and things are set up
 *	to put information in the EditCell first.
 *
 * ----------------------------------------------------------------------------
 */

void
CIFReadCellInit(
    int ptrkeys)
{
    int i;

    HashInit(&CifCellTable, 32, ptrkeys);
    cifReadCellDef = EditCellUse->cu_def;
    cifSubcellBeingRead = FALSE;
    cifCurReadPlanes = cifEditCellPlanes;
    for (i = 0; i < MAXCIFRLAYERS; i += 1)
    {
	if (cifEditCellPlanes[i] == NULL)
	    cifEditCellPlanes[i] = DBNewPlane((ClientData) TT_SPACE);
	if (cifSubcellPlanes[i] == NULL)
	    cifSubcellPlanes[i] = DBNewPlane((ClientData) TT_SPACE);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * cifForgetCell --
 *
 * 	This local procedure is used to find a cell in the subcell
 *	table and remove its CellDef entry.
 *
 * Results:
 *	FALSE if no such entry was found, otherwise TRUE.
 *
 * Side effects:
 *	Mucks with the CIF cell name hash table.
 *
 * ----------------------------------------------------------------------------
 */

bool
cifForgetCell(
    int cifNum)
{
    HashEntry *h;

    h = HashLookOnly(&CifCellTable, (char *)(spointertype)cifNum);
    if (h == NULL)
	return FALSE;
    else if (HashGetValue(h) == 0)
	return FALSE;

    HashSetValue(h, 0);
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cifUniqueCell --
 *
 *	Attempt to find a cell in the CIF subcell name hash table.
 *	If one exists, rename its definition so that it will not
 *	be overwritten when the cell is redefined.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
cifUniqueCell(
   int cifNum)
{
    HashEntry *h;
    CellDef *def, *testdef;
    char name[17];
    int reused = 0;

    h = HashLookOnly(&CifCellTable, (char *)(spointertype)cifNum);

    if ((h == NULL) || HashGetValue(h) == 0)
    {
	/* Cell was deleted with "DD".  Don't rename anything */
	return;
    }

    sprintf(name, "%d", cifNum);
    def = DBCellLookDef(name);
    if (def == (CellDef *)NULL)
	return;

    /* Cell may have been called but not yet defined---this is okay. */
    else if ((def->cd_flags & CDAVAILABLE) == 0)
        return;

    testdef = def;
    while (testdef != NULL)
    {
	sprintf(name, "%d_%d", cifNum, ++reused);
	testdef = DBCellLookDef(name);
    }
    DBCellRenameDef(def, name);

    h = HashFind(&CifCellTable, (char *)(spointertype)cifNum);
    HashSetValue(h, 0);

    CIFReadError("Warning: cell definition %d reused.\n", cifNum);
}

/*
 * ----------------------------------------------------------------------------
 *
 * cifFindCell --
 *
 * 	This local procedure is used to find a cell in the subcell
 *	table, and create a new subcell if there isn't already
 *	one there.  If a new subcell is created, its CDAVAILABLE
 *	is left FALSE.
 *
 * Results:
 *	The return value is a pointer to the definition for the
 *	cell whose CIF number is cifNum.
 *
 * Side effects:
 *	A new CellDef may be created.
 *
 * ----------------------------------------------------------------------------
 */

CellDef *
cifFindCell(
    int cifNum)			/* The CIF number of the desired cell. */
{
    HashEntry *h;
    CellDef *def, *testdef;
    int reused;

    h = HashFind(&CifCellTable, (char *)(spointertype)cifNum);

    if (HashGetValue(h) == 0)
    {
	char name[15];

	sprintf(name, "%d", cifNum);
	def = DBCellLookDef(name);
	if (def == NULL)
	{
	    def = DBCellNewDef(name);

	    /* Tricky point:  call DBReComputeBbox here to make SURE
	     * that the cell has a valid bounding box.  Otherwise,
	     * if the cell is used in a parent before being defined
	     * then it will cause a core dump.
	     */

	    DBReComputeBbox(def);
	}
	HashSetValue(h, def);
    }
    return (CellDef *) HashGetValue(h);
}

/*
 * ----------------------------------------------------------------------------
 *
 * CIFScalePlanes --
 *
 *   Scale all of the CIF planes by the amount (scalen / scaled)
 *
 * ----------------------------------------------------------------------------
 */

void
CIFScalePlanes(
    int scalen,
    int scaled,
    Plane **planearray)
{
    int pNum;
    Plane *newplane;

    for (pNum = 0; pNum < MAXCIFRLAYERS; pNum++)
    {
	if (planearray[pNum] != NULL)
	{
	    newplane = DBNewPlane((ClientData) TT_SPACE);
	    DBClearPaintPlane(newplane);
	    dbScalePlane(planearray[pNum], newplane, pNum,
			scalen, scaled, TRUE);
	    DBFreePaintPlane(planearray[pNum]);
	    TiFreePlane(planearray[pNum]);
	    planearray[pNum] = newplane;
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * CIFInputRescale --
 *
 *	Scale all CIF distances by n / d.  Normally, rescaling is done in
 *	the upward direction (n > 1, d = 1) in response to a value in the
 *	CIF input that does not divide evenly into the database units of
 *	the CIF planes.  Currently there is no call to CIFInputRescale
 *	with d > 1, but it is left for the possibility that scalefactors
 *	could be restored after finishing a subcell definition, possibly
 *	preventing integer overflow in large designs.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Many.  Adjusts the CIF input style scalefactor (CIF vs. magic
 *	units), multiplier (CIF units vs. centimicrons), distances of all
 *	CIF boolean operations, and the scale of all existing planes in
 *	the current CIF database.
 *
 * ----------------------------------------------------------------------------
 */

void
CIFInputRescale(
    int n,
    int d)
{
    CIFReadStyle *istyle = cifCurReadStyle;
    CIFReadLayer *cl;
    CIFOp *op;
    int i;

    /* 2-step process for efficiency */

    if (n > 1)
    {
	istyle->crs_scaleFactor *= n;
	istyle->crs_multiplier *= n;

	for (i = 0; i < istyle->crs_nLayers; i++)
	{
	    cl = istyle->crs_layers[i];
	    for (op = cl->crl_ops; op != NULL; op = op->co_next)
		if (op->co_distance)
		    op->co_distance *= n;
	}
    }

    if (d > 1)
    {
	istyle->crs_scaleFactor /= d;
	istyle->crs_multiplier /= d;

	for (i = 0; i < istyle->crs_nLayers; i++)
	{
	    cl = istyle->crs_layers[i];
	    for (op = cl->crl_ops; op != NULL; op = op->co_next)
		if (op->co_distance)
		    op->co_distance /= d;
	}
    }

    CIFScalePlanes(n, d, cifCurReadPlanes);
    if (cifCurReadPlanes != cifEditCellPlanes)
	CIFScalePlanes(n, d, cifEditCellPlanes);
    if ((const Plane*)cifEditCellPlanes != (const Plane*)cifSubcellPlanes && cifCurReadPlanes != cifSubcellPlanes)
	CIFScalePlanes(n, d, cifSubcellPlanes);

    CIFReadWarning("CIF style %s: units rescaled by factor of %d / %d\n",
		istyle->crs_name, n, d);
}

/*
 * ----------------------------------------------------------------------------
 *
 * CIFParseStart --
 *
 * 	Parse the beginning of a symbol (cell) definition.
 *	ds ::= D { blank } S integer [ sep integer sep integer ]
 *
 * Results:
 *	TRUE is returned if the parse completed successfully, and
 *	FALSE is returned otherwise.
 *
 * Side effects:
 *	Set up information for the new cell, including the CIF
 *	planes and creating a Magic cell (if one doesn't exist
 *	already).
 *
 * ----------------------------------------------------------------------------
 */

bool
CIFParseStart(void)
{
    int		number;

    if (cifSubcellBeingRead)
    {
	CIFReadError("definition start inside other definition; ignored.\n");
	CIFSkipToSemi();
	return FALSE;
    }
    if (cifSubcellId != NULL)
    {
	CIFReadError("pending call identifier %s discarded.\n", cifSubcellId);
	(void) StrDup(&cifSubcellId, (char *) NULL);
    }

    /* Take the `S'. */

    TAKE();
    if (!CIFParseInteger(&number))
    {
	CIFReadError("definition start, but no symbol number; ignored.\n");
	CIFSkipToSemi();
	return FALSE;
    }
    else if (number < 0)
    {
	CIFReadError("illegal negative symbol number; definition ignored.\n");
	CIFSkipToSemi();
	return FALSE;
    }

    if (!CIFParseInteger(&cifReadScale1))
    {
	cifReadScale1 = 1;
	cifReadScale2 = 1;
    }
    else
    {
	cifReadScale1 *= cifCurReadStyle->crs_multiplier;  /* Units not centimicrons */

	if (!CIFParseInteger(&cifReadScale2))
	{
	    CIFReadError(
	        "only one of two scale factors given; ignored.\n");
	    cifReadScale1 = 1;
	    cifReadScale2 = 1;
	}
    }

    if (cifReadScale1 <= 0 || cifReadScale2 <= 0)
    {
	CIFReadError("Illegal scale %d / %d changed to 1 / 1\n",
		cifReadScale1, cifReadScale2);
	cifReadScale1 = 1;
	cifReadScale2 = 1;
    }


    /*
     * Set up the cell definition.
     */

    cifUniqueCell(number);
    cifReadCellDef = cifFindCell(number);
    DBCellClearDef(cifReadCellDef);
    DBCellSetAvail(cifReadCellDef);

    cifOldReadPlane = cifReadPlane;
    cifReadPlane = (Plane *) NULL;
    cifSubcellBeingRead = TRUE;
    cifCurReadPlanes = cifSubcellPlanes;
    return TRUE;
}


/*
 * ----------------------------------------------------------------------------
 * cifCheckPaintFunc ---
 *
 *	Callback function for checking if any paint has been generated
 *	on the CIF target plane for a "copyup" layer.
 * ----------------------------------------------------------------------------
 */

int cifCheckPaintFunc(
    Tile *tile,
    TileType dinfo,
    ClientData clientData)
{
    return 1;
}

/* Callback function for copying paint from one CIF cell into another */

int
cifCopyPaintFunc(
    Tile *tile,
    TileType dinfo,
    CIFCopyRec *cifCopyRec)
{
    int pNum;
    TileType newdinfo;
    Rect sourceRect, targetRect;
    Transform *trans = cifCopyRec->trans;
    Plane *plane = cifCopyRec->plane;

    newdinfo = TiGetTypeExact(tile) | dinfo;

    if (trans)
    {
        TiToRect(tile, &sourceRect);
        GeoTransRect(trans, &sourceRect, &targetRect);
	if (IsSplit(tile))
	    newdinfo = DBTransformDiagonal(TiGetTypeExact(tile) | dinfo, trans);
    }
    else
        TiToRect(tile, &targetRect);

    DBNMPaintPlane(plane, newdinfo, &targetRect, CIFPaintTable,
                (PaintUndoInfo *)NULL);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cifMaskHintFunc --
 *
 * For each tile in the scanned plane, convert the tile into a coordinate
 * string by appending the coordinates to the list passed as clientData.
 *
 * Results:
 *	Return 0 to keep the search going.
 *
 * Side effects:
 *	Allocates memory for and seeds a linked rect entry
 *
 * ----------------------------------------------------------------------------
 */

int
cifMaskHintFunc(
    Tile *tile,
    TileType dinfo,	/* Unused, do not support non-manhattan hints */
    LinkedRect **lrecp)
{
    Rect r;
    LinkedRect *newlr;

    newlr = (LinkedRect *)mallocMagic(sizeof(LinkedRect));
    newlr->r_next = *lrecp;
    (*lrecp) = newlr;

    TiToRect(tile, &newlr->r_r);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CIFPaintCurrent --
 *
 * 	This procedure does geometrical processing on the current
 *	set of CIF planes, and paints the results into the current
 *	CIF cell.
 *
 * Results:
 *	Return 0
 *
 * Side effects:
 *	Lots of information gets added to the current Magic cell.
 *
 * ----------------------------------------------------------------------------
 */

int
CIFPaintCurrent(
    int filetype)
{
    /* Forward declarations. */
    extern int cifMakeBoundaryFunc(Tile *tile, TileType dinfo, ClientData clientdata);
    extern int cifPaintCurrentFunc(Tile *tile, TileType dinfo, TileType type);

    Plane *plane, *swapplane;
    int i;

    for (i = 0; i < cifCurReadStyle->crs_nLayers; i++)
    {
	TileType type;
	CIFOp *op;

	plane = CIFGenLayer(cifCurReadStyle->crs_layers[i]->crl_ops,
	    &TiPlaneRect, cifReadCellDef, cifReadCellDef,
	    cifCurReadPlanes, FALSE, (ClientData)NULL);

	/* Generate a paint/erase table, then paint from the CIF
	 * plane into the current Magic cell.
	 */
	type = cifCurReadStyle->crs_layers[i]->crl_magicType;

	if (cifCurReadStyle->crs_layers[i]->crl_flags & CIFR_TEMPLAYER)
	{
	    op = cifCurReadStyle->crs_layers[i]->crl_ops;
	    while (op)
	    {
		if (op->co_opcode == CIFOP_COPYUP) break;
		op = op->co_next;
	    }

	    /* Quick check to see if anything was generated	*/
	    /* on this layer.					*/

	    if (op && (DBSrPaintArea((Tile *)NULL, plane, &TiPlaneRect,
			&DBAllButSpaceBits, cifCheckPaintFunc,
			(ClientData)NULL) == 1))
	    {
		/* Copy-up function */

		int pNum;
		Plane *newplane;
		Plane **parray;
		extern char *(cifReadLayers[MAXCIFRLAYERS]);

		/* NOTE:  The condition cd_client == 0 when CDFLATGDS
		 * indicates that the cell was already in memory when the
		 * GDS was read.  This condition should be properly caught
		 * and handled.
		 */
		if ((cifReadCellDef->cd_flags & CDFLATGDS) &&
				(cifReadCellDef->cd_client != (ClientData)0))
		    parray = (Plane **)cifReadCellDef->cd_client;
		else
		{
		    parray = (Plane **)mallocMagic(MAXCIFRLAYERS * sizeof(Plane *));
		    cifReadCellDef->cd_flags |= CDFLATGDS;
		    cifReadCellDef->cd_flags &= ~CDFLATTENED;
		    cifReadCellDef->cd_client = (ClientData)parray;
		    for (pNum = 0; pNum < MAXCIFRLAYERS; pNum++)
			parray[pNum] = NULL;
		}

		for (pNum = 0; pNum < MAXCIFRLAYERS; pNum++)
		{
		    if (TTMaskHasType(&op->co_cifMask, pNum))
		    {
			CIFCopyRec cifCopyRec;

			newplane = parray[pNum];
			if (newplane == NULL)
		 	{
			    newplane = DBNewPlane((ClientData) TT_SPACE);
			    DBClearPaintPlane(newplane);
			}

			cifCopyRec.plane = newplane;
			cifCopyRec.trans = NULL;

			DBSrPaintArea((Tile *)NULL, plane, &TiPlaneRect,
				&DBAllButSpaceBits, cifCopyPaintFunc,
				&cifCopyRec);

			parray[pNum] = newplane;
		    }
		}
	    }
	    else if (op == NULL)
	    {
		LinkedRect *lrec = NULL, *lsrch;

		/* Handle boundary layer */

		op = cifCurReadStyle->crs_layers[i]->crl_ops;
		while (op)
		{
		    if (op->co_opcode == CIFOP_BOUNDARY) break;
		    op = op->co_next;
		}

		if (op && (DBSrPaintArea((Tile *)NULL, plane, &TiPlaneRect,
			&DBAllButSpaceBits, cifCheckPaintFunc,
			(ClientData)NULL) == 1))
		    DBSrPaintArea((Tile *) NULL, plane, &TiPlaneRect,
			&CIFSolidBits, cifMakeBoundaryFunc, INT2CD(filetype));

		/* Handle mask-hints input operator */

		op = cifCurReadStyle->crs_layers[i]->crl_ops;
		while (op)
		{
		    if (op->co_opcode == CIFOP_MASKHINTS) break;
		    op = op->co_next;
		}

		if (op && (DBSrPaintArea((Tile *)NULL, plane, &TiPlaneRect,
			&DBAllButSpaceBits, cifCheckPaintFunc,
			(ClientData)NULL) == 1))
		{
		    /* (To do:  remove the linked Rects and paint directly
		     * into the plane in cifMaskHintFunc())
		     */
		    DBSrPaintArea((Tile *) NULL, plane, &TiPlaneRect,
				&CIFSolidBits, cifMaskHintFunc,
				(ClientData)&lrec);

		    if (lrec != NULL)
		    {
			PropertyRecord *proprec, *proporig;
			char *propname, *layername;
			int proplen, i, savescale;
			bool origfound = FALSE;
			Plane *plane;
	
			layername = (char *)op->co_client;
		    	propname = (char *)mallocMagic(11 + strlen(layername));
		    	sprintf(propname, "MASKHINTS_%s", layername);
	
			/* If there is already a mask hint plane for this layer,
			 * then add to it;  otherwise, create a new plane.
			 */
			proprec = DBPropGet(cifReadCellDef, layername, &origfound);
			if (origfound)
			    plane = proprec->prop_value.prop_plane;
			else
			{
			    proprec = (PropertyRecord *)mallocMagic(
					sizeof(PropertyRecord));
			    proprec->prop_type = PROPERTY_TYPE_PLANE;
			    proprec->prop_len = 0;	/* (unused) */
			    plane = DBNewPlane((ClientData)TT_SPACE);
			    proprec->prop_value.prop_plane = plane;
			    DBPropPut(cifReadCellDef, propname, proprec);
			}

		    	while (lrec != NULL)
		    	{
			    lrec->r_r.r_xtop =
					CIFScaleCoord(lrec->r_r.r_xtop, COORD_EXACT);
			    savescale = cifCurReadStyle->crs_scaleFactor;
			    lrec->r_r.r_ytop =
					CIFScaleCoord(lrec->r_r.r_ytop, COORD_EXACT);
			    if (savescale != cifCurReadStyle->crs_scaleFactor)
			    {
				lrec->r_r.r_xtop *=
					(savescale / cifCurReadStyle->crs_scaleFactor);
				savescale = cifCurReadStyle->crs_scaleFactor;
			    }
			    lrec->r_r.r_xbot =
					CIFScaleCoord(lrec->r_r.r_xbot, COORD_EXACT);
			    if (savescale != cifCurReadStyle->crs_scaleFactor)
			    {
				lrec->r_r.r_xtop *=
					(savescale / cifCurReadStyle->crs_scaleFactor);
				lrec->r_r.r_ytop *=
					(savescale / cifCurReadStyle->crs_scaleFactor);
				savescale = cifCurReadStyle->crs_scaleFactor;
			    }
			    lrec->r_r.r_ybot =
					CIFScaleCoord(lrec->r_r.r_ybot, COORD_EXACT);
			    if (savescale != cifCurReadStyle->crs_scaleFactor)
			    {
				lrec->r_r.r_xtop *=
					(savescale / cifCurReadStyle->crs_scaleFactor);
				lrec->r_r.r_ytop *=
					(savescale / cifCurReadStyle->crs_scaleFactor);
				lrec->r_r.r_xbot *=
					(savescale / cifCurReadStyle->crs_scaleFactor);
			    }

			    DBPaintPlane(plane, &lrec->r_r, CIFPaintTable,
					(PaintUndoInfo *)NULL);
	
			    free_magic1_t mm1 = freeMagic1_init();
			    freeMagic1(&mm1, lrec);
			    lrec = lrec->r_next;
			    freeMagic1_end(&mm1);
			}
		    	freeMagic(propname);
		    }
		}
	    }

	    /* Swap planes */
	    swapplane = cifCurReadPlanes[type];
	    cifCurReadPlanes[type] = plane;
	    plane = swapplane;
	}
	else
	{
	    DBSrPaintArea((Tile *) NULL, plane, &TiPlaneRect,
			&CIFSolidBits, cifPaintCurrentFunc,
			INT2CD(type));
	}

	/* Recycle the plane, which was dynamically allocated. */

	DBFreePaintPlane(plane);
	TiFreePlane(plane);
    }

    /* If mask hints were requested, then for each GDS/CIF layer in the	*/
    /* input, if the layer has a corresponding output layer and the	*/
    /* output layer has a mask hints operator, then generate the output	*/
    /* plane for that layer, compare to the input plane, and create	*/
    /* mask hint properties to make the output the same as the input.	*/

    if ((CalmaMaskHints != NULL) && (!TTMaskIsZero(CalmaMaskHints)))
    {
	int j;
    	CIFOp *op, newop, subop;
	Plane *presult;
	TileTypeBitMask genMask;
	int *in_out_map;
	extern char *(cifReadLayers[MAXCIFRLAYERS]);

	TTMaskZero(&genMask);
	in_out_map = (int *)mallocMagic(cifNReadLayers * sizeof(int));

    	for (i = 0; i < cifNReadLayers; i++)
	{
	    if (!TTMaskHasType(CalmaMaskHints, i)) continue;

	    /* Does the input layer have a corresponding output layer? */
	    in_out_map[i] = -1;
	    for (j = 0; j < CIFCurStyle->cs_nLayers; j++)
	    {
		if (!strcmp(CIFCurStyle->cs_layers[j]->cl_name, cifReadLayers[i]))
		{
	    	    /* Does the layer have a mask-hints operator? */
	    	    for (op = CIFCurStyle->cs_layers[j]->cl_ops; op; op = op->co_next)
		    	if (op->co_opcode == CIFOP_MASKHINTS)
		    	{
			    TTMaskSetType(&genMask, j);
			    in_out_map[i] = j;
			    break;
		    	}
		}
		if (in_out_map[i] >= 0) break;
	    }
	}

	/* Multiply input planes to the same scale as the generated output */
	CIFScalePlanes(CIFCurStyle->cs_scaleFactor, cifCurReadStyle->crs_scaleFactor,
			cifCurReadPlanes);

	/* Generate the output for these layers from the cell contents */
	CIFClearPlanes(CIFPlanes);

	/* TO-DO:  Replace DBAllTypeBits with genMask.  Requires that genMask	*/
	/* be expanded to include all dependent layers.				*/
	CIFGen(cifReadCellDef, cifReadCellDef, &TiPlaneRect, CIFPlanes, &DBAllTypeBits,
			TRUE, FALSE, FALSE, (ClientData)NULL);

	/* Set up double operator for OR and ANDNOT functions */
	newop.co_opcode = CIFOP_OR;
	newop.co_distance = 0;
	newop.co_next = &subop;
	newop.co_client = (ClientData)NULL;
	TTMaskZero(&newop.co_paintMask);

	subop.co_opcode = CIFOP_ANDNOT;
	subop.co_distance = 0;
	subop.co_next = NULL;
	subop.co_client = (ClientData)NULL;
	TTMaskZero(&subop.co_paintMask);

    	for (i = 0; i < cifNReadLayers; i++)
	{
	    LinkedRect *lrec = NULL, *lsrch;
	    Plane *tempp;

	    if (!TTMaskHasType(CalmaMaskHints, i)) continue;

	    j = in_out_map[i];
	    if (j < 0) continue;

	    TTMaskSetOnlyType(&subop.co_cifMask, j);

	    /* Replace last layer + 1 on CIFPlanes with input layer i */

	    tempp = CIFPlanes[CIFCurStyle->cs_nLayers];
	    TTMaskSetOnlyType(&newop.co_cifMask, CIFCurStyle->cs_nLayers);
	    CIFPlanes[CIFCurStyle->cs_nLayers] = cifCurReadPlanes[i];
	    CIFCurStyle->cs_nLayers++;

	    /* Compute result (i AND-NOT j), which will be all the areas of
	     * the input on layer i that are not generated by writing output
	     * layer j.
	     */
	    presult = CIFGenLayer(&newop, &TiPlaneRect, (CellDef *)NULL,
			(CellDef *)NULL, CIFPlanes, FALSE, (ClientData)NULL);

	    /* Scan the resulting plane and generate linked Rect structures for
	     * each shape found.  (To do:  Remove the linked Rects and paint
	     * directly into the plane in cifMaskHintFunc(), which is more
	     * efficient but not hugely so.)
	     */
	    DBSrPaintArea((Tile *)NULL, presult, &TiPlaneRect, &CIFSolidBits,
			cifMaskHintFunc, (ClientData)&lrec);

	    if (lrec != NULL)
	    {
		PropertyRecord *proprec;
		bool propfound;
		char *propname;
		Plane *plane;

	    	propname = (char *)mallocMagic(11 + strlen(cifReadLayers[i]));
	    	sprintf(propname, "MASKHINTS_%s", cifReadLayers[i]);

	    	/* Paint all linked Rects into a mask-hints property plane
		 * in the target cell.
		 */

		proprec = DBPropGet(cifReadCellDef, propname, &propfound);
		if (!propfound)
		{
		    proprec = (PropertyRecord *)mallocMagic(sizeof(PropertyRecord));
		    proprec->prop_type = PROPERTY_TYPE_PLANE;
		    proprec->prop_len = 0;	/* (unused) */
		    plane = DBNewPlane((ClientData)TT_SPACE);
		    proprec->prop_value.prop_plane = plane;
		    DBPropPut(cifReadCellDef, propname, proprec);
		}
		else
		    plane = proprec->prop_value.prop_plane;

	    	while (lrec != NULL)
	    	{
		    lrec->r_r.r_xbot /= CIFCurStyle->cs_scaleFactor;
		    lrec->r_r.r_ybot /= CIFCurStyle->cs_scaleFactor;
		    lrec->r_r.r_xtop /= CIFCurStyle->cs_scaleFactor;
		    lrec->r_r.r_ytop /= CIFCurStyle->cs_scaleFactor;

		    DBPaintPlane(plane, &lrec->r_r, CIFPaintTable,
				(PaintUndoInfo *)NULL);

		    free_magic1_t mm1 = freeMagic1_init();
		    freeMagic1(&mm1, lrec);
		    lrec = lrec->r_next;
		    freeMagic1_end(&mm1);
		}
	    	freeMagic(propname);
	    }

	    /* Delete the generated plane */
	    DBFreePaintPlane(presult);
	    TiFreePlane(presult);

	    /* Replace the input plane that was shuffled out */
	    CIFCurStyle->cs_nLayers--;
	    CIFPlanes[CIFCurStyle->cs_nLayers] = tempp;
	}

	/* Free up the planes used to create the output */
	CIFClearPlanes(CIFPlanes);

	freeMagic((char *)in_out_map);
    }

    /* Now go through all the current planes and zero them out. */

    for (i = 0; i < MAXCIFRLAYERS; i++)
	DBClearPaintPlane(cifCurReadPlanes[i]);

    return 0;
}

/* Use CIF layer geometry to define a fixed bounding box for the current cell */

int
cifMakeBoundaryFunc(
    Tile *tile,			/* Tile of CIF information. */
    TileType dinfo,		/* Split tile information (unused) */
    ClientData clientdata)	/* Pass the file type (CIF or CALMA) */
{
    /* It is assumed that there is one rectangle for the boundary.  */
    /* If there are multiple rectangles defined with the boundary   */
    /* layer, then the last one defines the FIXED_BBOX property.    */

    PropertyRecord *proprec;
    Rect area;
    char propertyvalue[128], *storedvalue;
    int savescale;
    int filetype = (int)CD2INT(clientdata);

    TiToRect(tile, &area);
    area.r_xtop = CIFScaleCoord(area.r_xtop, COORD_EXACT);
    savescale = cifCurReadStyle->crs_scaleFactor;
    area.r_ytop = CIFScaleCoord(area.r_ytop, COORD_EXACT);
    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	area.r_xtop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	savescale = cifCurReadStyle->crs_scaleFactor;
    }
    area.r_xbot = CIFScaleCoord(area.r_xbot, COORD_EXACT);
    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	area.r_xtop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	area.r_ytop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	savescale = cifCurReadStyle->crs_scaleFactor;
    }
    area.r_ybot = CIFScaleCoord(area.r_ybot, COORD_EXACT);
    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	area.r_xtop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	area.r_ytop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	area.r_xbot *= (savescale / cifCurReadStyle->crs_scaleFactor);
    }

    if (cifReadCellDef->cd_flags & CDFIXEDBBOX)
    {
	PropertyRecord *proprec;
	bool found;

	/* Only flag a warning if the redefined boundary was	*/
	/* different from the original.				*/

	proprec = DBPropGet(cifReadCellDef, "FIXED_BBOX", &found);
	if (found)
	{
	    Rect bbox;
	    if ((proprec->prop_type == PROPERTY_TYPE_DIMENSION) &&
			(proprec->prop_len == 4))
	    {
		bbox.r_xbot = proprec->prop_value.prop_integer[0];
		bbox.r_ybot = proprec->prop_value.prop_integer[1];
		bbox.r_xtop = proprec->prop_value.prop_integer[2];
		bbox.r_ytop = proprec->prop_value.prop_integer[3];

		if ((bbox.r_xbot != area.r_xbot) ||
			(bbox.r_ybot != area.r_ybot) ||
			(bbox.r_xtop != area.r_xtop) ||
			(bbox.r_ytop != area.r_ytop))
		{
		    if (filetype == FILE_CIF)
			CIFReadError("Warning:  Cell %s boundary was redefined.\n",
				cifReadCellDef->cd_name);
		    else
			CalmaReadError("Warning:  Cell %s boundary was redefined.\n",
				cifReadCellDef->cd_name);
		}
	    }
	}
    }

    proprec = (PropertyRecord *)mallocMagic(intPropertyRecordSize(4));
    proprec->prop_type = PROPERTY_TYPE_DIMENSION;
    proprec->prop_len = 4;
    proprec->prop_value.prop_integer[0] = area.r_xbot;
    proprec->prop_value.prop_integer[1] = area.r_ybot;
    proprec->prop_value.prop_integer[2] = area.r_xtop;
    proprec->prop_value.prop_integer[3] = area.r_ytop;

    DBPropPut(cifReadCellDef, "FIXED_BBOX", proprec);
    cifReadCellDef->cd_flags |= CDFIXEDBBOX;
    return 0;
}

/* Paint CIF layer geometry into the current cell def as magic layer "type"	*/

int
cifPaintCurrentFunc(
    Tile *tile,			/* Tile of CIF information. */
    TileType dinfo,		/* Split tile information */
    TileType type)		/* Magic type to be painted. */
{
    Rect area;
    int pNum;
    int savescale;
    bool snap_type = COORD_EXACT;

    /* Contact types are allowed to be on half-lambda spacing, and are	*/
    /* snapped to the nearest magic coordinate if the result is a	*/
    /* fractional magic coordinate.					*/

    if (DBIsContact(type)) snap_type = COORD_HALF_U;

    /* Compute the area of the CIF tile, then scale it into
     * Magic coordinates.
     */

    TiToRect(tile, &area);
    area.r_xtop = CIFScaleCoord(area.r_xtop, snap_type);
    savescale = cifCurReadStyle->crs_scaleFactor;
    area.r_ytop = CIFScaleCoord(area.r_ytop, snap_type);
    if (snap_type == COORD_HALF_U) snap_type = COORD_HALF_L;
    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	area.r_xtop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	savescale = cifCurReadStyle->crs_scaleFactor;
    }
    area.r_xbot = CIFScaleCoord(area.r_xbot, snap_type);
    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	area.r_xtop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	area.r_ytop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	savescale = cifCurReadStyle->crs_scaleFactor;
    }
    area.r_ybot = CIFScaleCoord(area.r_ybot, snap_type);
    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	area.r_xtop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	area.r_ytop *= (savescale / cifCurReadStyle->crs_scaleFactor);
	area.r_xbot *= (savescale / cifCurReadStyle->crs_scaleFactor);
    }

    /* Check for degenerate areas (from rescale limiting) before painting */
    if ((area.r_xbot == area.r_xtop) || (area.r_ybot == area.r_ytop))
	return 0;

    for (pNum = PL_PAINTBASE; pNum < DBNumPlanes; pNum++)
	if (DBPaintOnPlane(type, pNum))
	{
	    DBNMPaintPlane(cifReadCellDef->cd_planes[pNum], TiGetTypeExact(tile) | dinfo,
		    &area, DBStdPaintTbl(type, pNum), (PaintUndoInfo *) NULL);
	}

    return  0;		/* To keep the search alive. */
}

/*
 * ----------------------------------------------------------------------------
 *
 * CIFParseFinish --
 *
 * 	This procedure is called at the end of a cell definition.
 *
 * Results:
 *	TRUE is returned if the parse completed successfully, and
 *	FALSE is returned otherwise.
 *
 * Side effects:
 *	Process the CIF planes and paint the results into the Magic
 *	cell.
 *
 * ----------------------------------------------------------------------------
 */

bool
CIFParseFinish(void)
{
    if (!cifSubcellBeingRead)
    {
	CIFReadError("definition finish without definition start; ignored.\n");
	CIFSkipToSemi();
	return FALSE;
    }
    if (cifSubcellId != NULL)
    {
	CIFReadError("pending call identifier %s discarded.\n", cifSubcellId);
	(void) StrDup(&cifSubcellId, (char *) NULL);
    }

    /* Take the `F'. */

    TAKE();

    /* Do the geometrical processing and paint this material back into
     * the appropriate cell of the database.  Then restore the saved
     * layer info.
     */

    CIFPaintCurrent(FILE_CIF);

    DBAdjustLabels(cifReadCellDef, &TiPlaneRect);
    DBReComputeBbox(cifReadCellDef);
    cifReadCellDef = EditCellUse->cu_def;
    cifReadPlane = cifOldReadPlane;
    cifReadScale1 = 1;
    cifReadScale2 = 1;
    cifSubcellBeingRead = FALSE;
    cifCurReadPlanes = cifEditCellPlanes;
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CIFParseDelete --
 *
 * 	This procedure is called to handle delete-symbol statements.
 *
 * Results:
 *	TRUE is returned if the parse completed successfully, and
 *	FALSE is returned otherwise.
 *
 * Side effects:
 *	The mapping between numbers and cells is modified to eliminate
 *	some symbols.
 *
 * ----------------------------------------------------------------------------
 */

bool
CIFParseDelete(void)
{
    int		number;

    /* Take the `D'. */

    TAKE();
    if (!CIFParseInteger(&number))
    {
	CIFReadError("definition delete, but no symbol number; ignored.\n");
	CIFSkipToSemi();
	return FALSE;
    }

    /* Unlink the hash entry from its target definition */
    cifForgetCell(number);

    CIFSkipToSemi();
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cifParseName --
 *
 * 	Parse a name, which is a string of alphabetics, numerics,
 *	or underscores, possibly preceded by whitespace.
 *
 * Results:
 *	The return value is a pointer to the name read from the
 *	CIF file.  This is a statically-allocated area, so the
 *	caller should copy out of this area before invoking this
 *	procedure again.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

char *
cifParseName(void)
{
    char	ch;
    char	*bufferp;
    static char	buffer[128];

    /* Skip white space. */

    for (ch = PEEK() ; ch == ' ' || ch == '\t' ; ch = PEEK())
	TAKE();

    /* Read the string. */

    bufferp = &buffer[0];
    for (ch = PEEK() ; (! isspace(ch)) && ch != ';' ; ch = PEEK())
    {
	*bufferp++ = TAKE();
    }
    *bufferp = '\0';
    return buffer;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cifParseUser9 --
 *
 * 	This procedure processes user extension 9: the name of the
 *	current symbol (cell) definition.
 *
 * Results:
 *	TRUE is returned if the parse completed successfully, and
 *	FALSE is returned otherwise.
 *
 * Side effects:
 *	The current CIF symbol is renamed from its default "cifxx" name
 *	to the given name.
 *
 * ----------------------------------------------------------------------------
 */

bool
cifParseUser9(void)
{
    char *name;

    name = cifParseName();
    if (!DBCellRenameDef(cifReadCellDef, name))
    {
	CIFReadError("%s already exists, so cell from CIF is named %s.\n",
		name, cifReadCellDef->cd_name);
    }
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CIFParseCall --
 *
 * 	This procedure processes subcell uses.  The syntax of a call is
 *	call ::= C integer transform
 *
 * Results:
 *	TRUE is returned if the parse completed successfully, and
 *	FALSE is returned otherwise.
 *
 * Side effects:
 *	A subcell is added to the current Magic cell we're generating.
 *
 * ----------------------------------------------------------------------------
 */

bool
CIFParseCall(void)
{
    int		called;
    Transform	transform;
    CellUse 	*use;
    CellDef	*def;

    /* Take the `C'. */

    TAKE();
    if (!CIFParseInteger(&called))
    {
	CIFReadError("call, but no symbol number; ignored.\n");
	CIFSkipToSemi();
	return FALSE;
    }

    /* Get optional transformation. */

    (void) CIFParseTransform(&transform);

    def = cifFindCell(called);

    /* avoid recursion
     */

    if (DBIsAncestor(def, cifReadCellDef))
    {
	CIFReadError("attempt to place cell use inside its own definition!\n");
	CIFSkipToSemi();
	return FALSE;
    }

    /* Find the use and add it to the current cell.  Give it an
     * id also.
     */

    use = DBCellNewUse(def, cifSubcellId);
    (void) DBLinkCell(use, cifReadCellDef);
    DBSetTrans(use, &transform);
    DBPlaceCell(use, cifReadCellDef);

    (void) StrDup(&cifSubcellId, (char *) NULL);
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cifParseUser91 --
 *
 * 	This procedure handles 91 user commands, which provide id's
 *	for following cell calls.  The syntax is:
 *	91 ::= 91 blanks name
 *
 * Results:
 *	TRUE is returned if the parse completed successfully, and
 *	FALSE is returned otherwise.
 *
 * Side effects:
 *	The identifier is saved until the call is read.  Then it is
 *	used as the identifier for the cell.
 *
 * ----------------------------------------------------------------------------
 */

bool
cifParseUser91(void)
{
    if (cifSubcellId != NULL)
    {
	CIFReadError("91 command with identifier %s pending; %s discarded.\n" ,
	    cifSubcellId , cifSubcellId);
    }
    (void) StrDup(&cifSubcellId, cifParseName());
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cifParseUser94 --
 *
 * 	This procedure parses 94 user commands, which are labelled
 *	points.  The syntax is:
 *	94 ::= 94 blanks name point
 *
 * Results:
 *	TRUE is returned if the parse completed successfully, and
 *	FALSE is returned otherwise.
 *
 * Side effects:
 *	A label is added to the current cell.
 *
 * ----------------------------------------------------------------------------
 */

bool
cifParseUser94(void)
{
    Rect rectangle;
    char *name = NULL;
    TileType type;
    int layer, flags, i;
    int savescale;

    (void) StrDup(&name, cifParseName());
    if (! CIFParsePoint(&rectangle.r_ll, 1))
    {
	CIFReadError("94 command, but no location; ignored.\n");
	CIFSkipToSemi();
	return FALSE;
    }

    /* Scale the coordinates, then make the location into a
     * rectangle.
     */

    rectangle.r_xbot = CIFScaleCoord(rectangle.r_xbot, COORD_ANY);
    savescale = cifCurReadStyle->crs_scaleFactor;
    rectangle.r_ybot = CIFScaleCoord(rectangle.r_ybot, COORD_ANY);
    if (savescale != cifCurReadStyle->crs_scaleFactor)
	rectangle.r_xbot *= (savescale / cifCurReadStyle->crs_scaleFactor);

    rectangle.r_ur = rectangle.r_ll;

    /* Get a layer, lookup the layer, then add the label to the
     * current cell.  Tricky business: in order for the default
     * label location to be computed
     */

    CIFSkipBlanks();
    if (PEEK() != ';')
    {
	char *lname = cifParseName();
	layer = CIFReadNameToType(lname, FALSE);
	if (layer < 0)
	{
	    CIFReadError("label attached to unknown layer %s.\n",
		    lname);
	    type = TT_SPACE;
	}
	else {
	    type = cifCurReadStyle->crs_labelLayer[layer];
	}
    } else {
	type = cifCurLabelType;

	/* Should do this better, by defining cifCurLabelFlags. . . */
	layer = -1;
	for (i = 0; i < cifCurReadStyle->crs_nLayers; i++)
	    if (cifCurReadStyle->crs_labelLayer[i] == type) {
		layer = i;
		break;
	    }
    }
    if (type >=0 )
    {
	if (layer >= 0 && cifCurReadStyle->crs_labelSticky[layer])
	    flags = LABEL_STICKY;
	else
	    flags = 0;
    	(void) DBPutLabel(cifReadCellDef, &rectangle, -1, name, type, flags, 0);
    }
    freeMagic(name);
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cifParseUser95 --
 *
 * 	This procedure parses 95 user commands, which are labelled
 *	points.  The syntax is:
 *	95 ::= 95 blanks name length width point
 *
 * Results:
 *	TRUE is returned if the parse completed successfully, and
 *	FALSE is returned otherwise.
 *
 * Side effects:
 *	An area label is added to the current cell.
 *
 * ----------------------------------------------------------------------------
 */

bool
cifParseUser95(void)
{
    /* Modified by BIM 1/8/2018 */
    Rect rectangle;
    Point size, center, lowerleft, upperright;
    char *name = NULL;
    TileType type;
    int layer, i;
    int savescale;

    (void) StrDup(&name, cifParseName());

    if (! CIFParsePoint(&size, 1))
    {
	CIFReadError("95 command, but no size; ignored.\n");
	CIFSkipToSemi();
	return FALSE;
    }

    savescale = cifCurReadStyle->crs_scaleFactor;

    /* The center coordinates returned are in CIF units *2              */
    /* the values will be halved later before conversion to magic units */

    if (! CIFParsePoint(&center, 2))
    {
	CIFReadError("95 command, but no location; ignored.\n");
	CIFSkipToSemi();
	return FALSE;
    }

    /* If reading the center causes a CIF input scale to be redefined,	*/
    /* then the length and width must also be changed.			*/

    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	size.p_x *= (cifCurReadStyle->crs_scaleFactor / savescale);
	size.p_y *= (cifCurReadStyle->crs_scaleFactor / savescale);
    }

    /* Scale the coordinates and create the rectangular area.		*/
    /* Explicitly calculate lowerleft and upperright using CIF units *2 */
    /* so that half-lambda centers are resolved before remapping to	*/
    /* magic coordinates.						*/

    lowerleft.p_x = center.p_x - size.p_x;
    lowerleft.p_y = center.p_y - size.p_y;

    upperright.p_x = center.p_x + size.p_x;
    upperright.p_y = center.p_y + size.p_y;

    if ((lowerleft.p_x % 2 == 0) && (lowerleft.p_y % 2 == 0)) {

      /* if possible convert values to CIF units by dividing by two */

      lowerleft.p_x /= 2;
      lowerleft.p_y /= 2;

      upperright.p_x /= 2;
      upperright.p_y /= 2;

    } else {

      /* if division by two would create inaccuracy then rescale to accommodate */

      CIFInputRescale(2, 1);

    }

    /* now scale each of the co-ordinates in turn */

    lowerleft.p_x = CIFScaleCoord(lowerleft.p_x, COORD_ANY);
    savescale = cifCurReadStyle->crs_scaleFactor;

    lowerleft.p_y = CIFScaleCoord(lowerleft.p_y, COORD_ANY);
    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	lowerleft.p_x *= (savescale / cifCurReadStyle->crs_scaleFactor);
	savescale = cifCurReadStyle->crs_scaleFactor;
    }

    upperright.p_x = CIFScaleCoord(upperright.p_x, COORD_ANY);
    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	lowerleft.p_x *= (savescale / cifCurReadStyle->crs_scaleFactor);
	lowerleft.p_y *= (savescale / cifCurReadStyle->crs_scaleFactor);
	savescale = cifCurReadStyle->crs_scaleFactor;
    }

    upperright.p_y = CIFScaleCoord(upperright.p_y, COORD_ANY);
    if (savescale != cifCurReadStyle->crs_scaleFactor)
    {
	lowerleft.p_x *= (savescale / cifCurReadStyle->crs_scaleFactor);
	lowerleft.p_y *= (savescale / cifCurReadStyle->crs_scaleFactor);
	upperright.p_x *= (savescale / cifCurReadStyle->crs_scaleFactor);
    }

    rectangle.r_xbot = lowerleft.p_x;
    rectangle.r_ybot = lowerleft.p_y;
    rectangle.r_xtop = upperright.p_x;
    rectangle.r_ytop = upperright.p_y;

    /* Get a layer, lookup the layer, then add the label to the
     * current cell.  Tricky business: in order for the default
     * label location to be computed
     */
    CIFSkipBlanks();
    if (PEEK() != ';')
    {
	char *name = cifParseName();
	layer = CIFReadNameToType(name, FALSE);
	if (layer < 0)
	{
	    CIFReadError("label attached to unknown layer %s.\n",
		    name);
	    type = TT_SPACE;
	}
	else type = cifCurReadStyle->crs_labelLayer[layer];
    }
    else {
	type = TT_SPACE;
	layer = -1;
	for (i = 0; i < cifCurReadStyle->crs_nLayers; i++)
	    if (cifCurReadStyle->crs_labelLayer[i] == type) {
		layer = i;
		break;
	    }
    }
    if (type >=0 )
    {
	int flags;
	if (layer >= 0 && cifCurReadStyle->crs_labelSticky[layer])
	    flags = LABEL_STICKY;
	else
	    flags = 0;
    	(void) DBPutLabel(cifReadCellDef, &rectangle, -1, name, type, flags, 0);
    }

    freeMagic(name);
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CIFParseUser --
 *
 * 	This procedure is called to process user-defined statements.
 *	The syntax is user ::= digit usertext.
 *
 * Results:
 *	TRUE is returned if the parse completed successfully, and
 *	FALSE is returned otherwise.
 *
 * Side effects:
 *	Depends on the user command.
 *
 * ----------------------------------------------------------------------------
 */
bool
CIFParseUser(void)
{
    char	ch;

    ch = TAKE();
    switch (ch)
    {
	case '9':
		ch = PEEK();
		switch (ch)
		{
		    case '1':
			(void) TAKE();
			return cifParseUser91();
		    case '4':
			(void) TAKE();
			return cifParseUser94();
		    case '5':
			(void) TAKE();
			return cifParseUser95();
		    default:
			if (isspace(ch)) return cifParseUser9();
		}
	default:
		CIFReadError("unimplemented user extension; ignored.\n");
		CIFSkipToSemi();
		return FALSE;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * CIFReadCellCleanup --
 *
 * 	This procedure is called after processing the CIF file.
 *	It performs various cleanup functions on the cells that
 *	have been read in.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The area of each cell is DRC'ed and redisplayed.  Error
 *	messages are output for any cells whose contents weren't
 *	in the CIF file.  An error message is also output if
 *	we're still in the middle of reading a subcell.
 *
 * ----------------------------------------------------------------------------
 */

void
CIFReadCellCleanup(
    int filetype)
{
    HashEntry *h;
    HashSearch hs;
    CellDef *def;
    MagWindow *window;
    int flags;

    if (cifSubcellBeingRead)
    {
	if (filetype == FILE_CIF)
	    CIFReadError("CIF ended partway through a symbol definition.\n");
	else
	    CalmaReadError("GDS ended partway through a symbol definition.\n");
	(void) CIFParseFinish();
    }

    HashStartSearch(&hs);
    while (TRUE)
    {
	h = HashNext(&CifCellTable, &hs);
	if (h == NULL) break;

	def = (CellDef *) HashGetValue(h);
	if (def == NULL)
	{
	    if (filetype == FILE_CIF)
		CIFReadError("cell table has NULL entry (Magic error).\n");
	    else
		CalmaReadError("cell table has NULL entry (Magic error).\n");
	    continue;
	}
	flags = def->cd_flags;
	if (!(flags & CDAVAILABLE))
	{
	    if (filetype == FILE_CIF)
		CIFReadError("cell %s was used but not defined.\n", def->cd_name);
	    else
		CalmaReadError("cell %s was used but not defined.\n", def->cd_name);
        }
	def->cd_flags &= ~CDPROCESSEDGDS;

	if ((filetype == FILE_CIF && CIFNoDRCCheck == FALSE) ||
			(filetype == FILE_CALMA && CalmaNoDRCCheck == FALSE))
	    DRCCheckThis(def, TT_CHECKPAINT, &def->cd_bbox);
	DBWAreaChanged(def, &def->cd_bbox, DBW_ALLWINDOWS, &DBAllButSpaceBits);
	DBCellSetModified(def, TRUE);

	/* Only mark cell as needing a timestamp update if the timestamp is zero */
	if (def->cd_timestamp != 0)
	    def->cd_flags &= ~CDGETNEWSTAMP;
    }

    /* Do geometrical processing on the top-level cell. */
    if (filetype == FILE_CIF) CIFPaintCurrent(filetype);

    DBAdjustLabels(EditCellUse->cu_def, &TiPlaneRect);
    DBReComputeBbox(EditCellUse->cu_def);
    DBWAreaChanged(EditCellUse->cu_def, &EditCellUse->cu_def->cd_bbox,
	    DBW_ALLWINDOWS, &DBAllButSpaceBits);
    DBCellSetModified(EditCellUse->cu_def, TRUE);

    /* Clean up saved CIF/GDS planes in cd_client records of cells */

    HashStartSearch(&hs);
    while (TRUE)
    {
	h = HashNext(&CifCellTable, &hs);
	if (h == NULL) break;

	def = (CellDef *) HashGetValue(h);
	if (def == NULL) continue;

	if (def->cd_flags & CDFLATGDS)
	{
	    /* These cells have been flattened and are no longer needed. */
	    /* Do not remove the actual CellDef, though, because it is	 */
	    /* still instanced, and that instance tells the GDS write	 */
	    /* routine that the subcell needs to be included in the 	 */
	    /* output.  But all clientdata and labels should be removed. */

	    int pNum;
	    Plane **cifplanes = (Plane **)def->cd_client;

	    UndoDisable();

	    /* cifplanes should be valid, but don't crash magic if not */
	    if (cifplanes != (Plane **)0)
	    {
		for (pNum = 0; pNum < MAXCIFRLAYERS; pNum++)
		{
		    if (cifplanes[pNum] != NULL)
		    {
			DBFreePaintPlane(cifplanes[pNum]);
			TiFreePlane(cifplanes[pNum]);
		    }
		}
		freeMagic((char *)def->cd_client);
	    }
	    def->cd_client = (ClientData)0;
	    def->cd_flags &= ~CDFLATGDS;

	    UndoEnable();
	}
    }
    HashKill(&CifCellTable);
}
