/*
 * CalmaReadcell.c --
 *
 * Input of Calma GDS-II stream format.
 * Processing for cells.
 *
 *     ********************************************************************* 
 *     * 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-7.5/calma/CalmaRdcl.c,v 1.5 2010/06/25 13:59:51 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>

#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/utils.h"
#include "utils/hash.h"
#include "database/database.h"
#include "database/databaseInt.h"
#include "utils/malloc.h"
#include "utils/tech.h"
#include "cif/cif.h"
#include "cif/CIFint.h"
#include "cif/CIFread.h"
#include "utils/signals.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"
#include "utils/styles.h"
#include "textio/textio.h"
#include "calma/calmaInt.h"
#include "calma/calma.h"

int calmaNonManhattan;

extern void CIFPaintCurrent();

extern HashTable calmaDefInitHash;

/* forward declarations */
void calmaElementSref();
bool calmaParseElement();

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

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

/* Added by NP 8/11/04 */

/*
 * ----------------------------------------------------------------------------
 *
 * calmaSetPosition --
 *
 *  This routine sets the file pointer calmaInputFile to the start
 *  of the CellDefinition "sname". It starts the search from the
 *  current position and looks forward to find the Cell Definition
 *  named "sname".
 *
 * Results:
 *	Current position of file pointer before it jumps to the
 *	definition of cell "sname" (if it exists, otherwise, returns
 *	end-of-file).
 *
 * Side Effects:
 *	The file position is set to the definition of cell "sname". If
 *	"sname" does not exist in the GDS stream file, the pointer is
 *	set to the end of the file.
 *
 * ----------------------------------------------------------------------------
 */

off_t
calmaSetPosition(sname)
    char *sname;
{
    off_t originalPos = 0, currentPos = 0;
    int nbytes, rtype;
    char *strname = NULL;
    int strRecSize = 0;

    originalPos = ftello(calmaInputFile);

    while (feof(calmaInputFile) == 0)
    {
        do
        {
             READRH(nbytes, rtype);      /* Read header	*/
             if (nbytes <= 0) goto complete;

             /* Skip no of bytes in record header until
              * we reach to next structure record.
              */
             fseek(calmaInputFile, nbytes - CALMAHEADERLENGTH, SEEK_CUR);
        } while (rtype != CALMA_BGNSTR);

        calmaReadStringRecord(CALMA_STRNAME, &strname);
        if ((strcmp(sname, strname)) == 0)
        {
             /* If name if structure matches with given name,
              * we got that Cell Defination. Set position of
              * file to start of that structure.
              */
             strRecSize = strlen(strname);
             if (strRecSize & 01) strRecSize++;
             fseek(calmaInputFile, -(nbytes + strRecSize + CALMAHEADERLENGTH),
				SEEK_CUR);
             break;
        }
     }

complete:
     /* Done with strname */
     if (strname != NULL) freeMagic(strname);
     return originalPos;
}

/* Added by NP 8/11/04 */

/*
 * ----------------------------------------------------------------------------
 *
 * calmaNextCell --
 *
 *   This routine sets the file pointer to next Cell Definition
 *   in the GDS stream. It goes only in the forward direction
 *   from the current position of the file pointer.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	File pointer set to start of next Cell definition.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaNextCell()
{
    int nbytes, rtype;

    if (feof(calmaInputFile) == 0)
    {
        do
        {
             READRH(nbytes, rtype);      /* Read header */
             if (nbytes <= 0)
             {
                /* We have reached the end of the file.  There are no
		 * more cell definitions remaining to read.
                 * Set file pointer to CALMA_ENDLIB record.
		 */
                fseek(calmaInputFile, -(CALMAHEADERLENGTH), SEEK_END);
                return;
             }

             /* Skip no. of bytes in record header to reach the next
	      * structure record.
              */
             fseek(calmaInputFile, nbytes - CALMAHEADERLENGTH, SEEK_CUR);
        } while(rtype != CALMA_BGNSTR);
        fseek(calmaInputFile, -nbytes, SEEK_CUR);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaExact --
 *
 * When dictated to flatten small cells (e.g., vias which have been placed
 * into their own cells to make use of GDS arrays), instead of calling
 * CIFPaintCurrent(), we just transfer the planes of cifCurReadPlanes to
 * the current cell, and swap the planes from the current cell into the
 * pointer array for cifCurReadPlanes, so we don't have to free and
 * reallocate any memory for the operation.  Flag the cell as being a
 * CIF cell, so that it can be destroyed after read-in and flattening.
 *
 * Results:
 *	Pointer to the plane structure created to hold the GDS data.
 *
 * Side Effects:
 *	Swaps the planes of cifReadCellDef with cifCurReadPlanes.
 *
 * ----------------------------------------------------------------------------
 */

Plane **
calmaExact()
{
    int pNum;
    Plane *newplane;
    Plane **parray;
    int gdsCopyPaintFunc();	/* Forward reference */

    parray = (Plane **)mallocMagic(MAXCIFRLAYERS * sizeof(Plane *));

    for (pNum = 0; pNum < MAXCIFRLAYERS; pNum++)
    {
	if (cifCurReadPlanes[pNum] != NULL)
	{
	    GDSCopyRec gdsCopyRec;

	    newplane = DBNewPlane((ClientData) TT_SPACE);
	    DBClearPaintPlane(newplane);

	    gdsCopyRec.plane = newplane;
	    gdsCopyRec.trans = NULL;
	    DBSrPaintArea((Tile *)NULL, cifCurReadPlanes[pNum], &TiPlaneRect,
		&DBAllButSpaceBits, gdsCopyPaintFunc, &gdsCopyRec);
	    parray[pNum] = newplane;
	}
	else
	    parray[pNum] = NULL;
    }

    /* Clear out the current paint planes for the next cell */
    for (pNum = 0; pNum < MAXCIFRLAYERS; pNum++)
	DBClearPaintPlane(cifCurReadPlanes[pNum]);

    return parray;
}



/*
 * ----------------------------------------------------------------------------
 *
 * calmaParseStructure --
 *
 * Process a complete GDS-II structure (cell) including its closing
 * CALMA_ENDSTR record.  In the event of a syntax error, we skip
 * ahead to the closing CALMA_ENDSTR, output a warning, and keep going.
 *
 * Results:
 *	TRUE if successful, FALSE if the next item in the input is
 *	not a structure.
 *
 * Side effects:
 *	Reads a new cell.
 *	Consumes input.
 *
 * ----------------------------------------------------------------------------
 */

bool
calmaParseStructure(filename)
    char *filename;		/* Name of the GDS file read */
{
    static int structs[] = { CALMA_STRCLASS, CALMA_STRTYPE, -1 };
    int nbytes, rtype, nsrefs, osrefs, npaths;
    char *strname = NULL, newname[CALMANAMELENGTH*2];
    HashEntry *he;
    int suffix;
    int mfactor;
    off_t filepos;
    bool was_called;
    CellDef *def;

    /* Make sure this is a structure; if not, let the caller know we're done */
    PEEKRH(nbytes, rtype);
    if (nbytes <= 0 || rtype != CALMA_BGNSTR)
	return (FALSE);

    /* Read the structure name */
    if (!calmaSkipExact(CALMA_BGNSTR)) goto syntaxerror;;
    if (!calmaReadStringRecord(CALMA_STRNAME, &strname)) goto syntaxerror;
    TxPrintf("Reading \"%s\".\n", strname);

    if (CalmaReadOnly)
	filepos = ftello(calmaInputFile);

    /* Set up the cell definition */
    he = HashFind(&calmaDefInitHash, strname);
    if ((def = (CellDef *)HashGetValue(he)) != NULL)
    {
	if (def->cd_flags & CDPROCESSEDGDS)
	{
	    /* If cell definition was marked as processed, then skip */
	    calmaNextCell();
	    return TRUE;
	}
	else
	{
	    for (suffix = 1; HashGetValue(he) != NULL; suffix++)
	    {
		(void) sprintf(newname, "%s_%d", strname, suffix);
		he = HashFind(&calmaDefInitHash, newname);
	    }
	    calmaReadError("Cell \"%s\" was already defined in this file.\n", strname);
	    calmaReadError("Giving this cell a new name: %s\n", newname);
	    strncpy(strname, newname, CALMANAMELENGTH*2);
	}
    }
    cifReadCellDef = calmaFindCell(strname, &was_called);
    DBCellClearDef(cifReadCellDef);
    DBCellSetAvail(cifReadCellDef);
    HashSetValue(he, cifReadCellDef);
    cifCurReadPlanes = cifSubcellPlanes;

    /* Done with strname */
    if (strname != NULL) freeMagic(strname);

    /* For read-only cells, set flag in def */
    if (CalmaReadOnly)
	cifReadCellDef->cd_flags |= CDVENDORGDS;

    /* Skip CALMA_STRCLASS or CALMA_STRTYPE */
    calmaSkipSet(structs);

    /* Initialize the hash table for layer errors */
    HashInit(&calmaLayerHash, 32, sizeof (CalmaLayerType) / sizeof (unsigned));

    /* Body of structure: a sequence of elements */
    osrefs = nsrefs = 0;
    npaths = 0;
    calmaNonManhattan = 0;
    while (calmaParseElement(&nsrefs, &npaths))
    {
	if (SigInterruptPending)
	    goto done;
	if (nsrefs > osrefs && (nsrefs % 100) == 0)
	    TxPrintf("    %d uses\n", nsrefs);
	osrefs = nsrefs;
#ifdef NONMANHATTAN
	calmaNonManhattan = 0;
    }
#else
    }
    if (calmaNonManhattan)
	TxPrintf("    %d non-Manhattan paths converted to stair-steps.\n",
		calmaNonManhattan);
#endif

    /* Make sure it ends with an ENDSTR record */
    if (!calmaSkipExact(CALMA_ENDSTR)) goto syntaxerror;

    if (CalmaReadOnly)
    {
	/* Writing the file position into a string is slow, but */
	/* it prevents requiring special handling when printing	*/
	/* out the properties.					*/

	char *fpcopy = (char *)mallocMagic(20);
	char *fncopy = StrDup(NULL, filename);
	sprintf(fpcopy, "%d", filepos);
	DBPropPut(cifReadCellDef, "GDS_START", (ClientData)fpcopy);

	fpcopy = (char *)mallocMagic(20);
	filepos = ftello(calmaInputFile);
	sprintf(fpcopy, "%d", filepos);
	DBPropPut(cifReadCellDef, "GDS_END", (ClientData)fpcopy);

	DBPropPut(cifReadCellDef, "GDS_FILE", (ClientData)fncopy);

	/* Do not lock the cell, or else we can't save the	*/
	/* magic cell with its GDS pointers to disk. . .	*/
	/* cifReadCellDef->cd_flags |= CDNOEDIT; */
    }

    /*
     * Don't paint now, just keep the CIF planes, and flatten the
     * cell by painting when instanced.  But---if this cell was
     * instanced before it was defined, then it can't be flattened.
     */
    if (CalmaFlattenUses && (!was_called) && (npaths < 10) && (nsrefs == 0))
    {
	TxPrintf("Flattening cell %s\n", cifReadCellDef->cd_name);
	cifReadCellDef->cd_client = (ClientData) calmaExact();
	cifReadCellDef->cd_flags |= CDFLATGDS;
    }
    else
    {
	/*
	 * Do the geometrical processing and paint this material back into
	 * the appropriate cell of the database.
	 */

	CIFPaintCurrent();
    }

    DBAdjustLabelsNew(cifReadCellDef, &TiPlaneRect,
	      (cifCurReadStyle->crs_flags & CRF_NO_RECONNECT_LABELS) ? 1 : 0);
    DBReComputeBbox(cifReadCellDef);

    /* Don't bother to register with DRC if we're going to delete the	*/
    /* cell, or if the cell is read-only.				*/

    if (!CalmaFlattenUses || (npaths >= 10) || (nsrefs != 0))
	if (!CalmaReadOnly)
	    DRCCheckThis(cifReadCellDef, TT_CHECKPAINT, &cifReadCellDef->cd_bbox);

    DBWAreaChanged(cifReadCellDef, &cifReadCellDef->cd_bbox,
	DBW_ALLWINDOWS, &DBAllButSpaceBits);
    DBCellSetModified(cifReadCellDef, TRUE);

    /*
     * Assign use-identifiers to all the cell uses.
     * These identifiers are generated so as to be
     * unique.
     */
    DBGenerateUniqueIds(cifReadCellDef, FALSE);
    cifReadCellDef->cd_flags |= CDPROCESSEDGDS;

done:
    HashKill(&calmaLayerHash);
    return (TRUE);

    /* Syntax error: skip to CALMA_ENDSTR */
syntaxerror:
    HashKill(&calmaLayerHash);
    return (calmaSkipTo(CALMA_ENDSTR));
}


/*
 * ----------------------------------------------------------------------------
 *
 * calmaParseElement --
 *
 * Process one element from a GDS-II structure, including its
 * trailing CALMA_ENDEL record.  In the event of a syntax error, we skip
 * ahead to the closing CALMA_ENDEL, output a warning, and keep going.
 *
 * Results:
 *	TRUE if we processed an element, FALSE when we reach something that
 *	is not an element.  In the latter case, we leave the non-element
 *	record unconsumed.
 *
 * Side effects:
 *	Consumes input.
 *	Depends on the kind of element encountered.
 *	If we process a SREF or AREF, increment *pnsrefs.
 *
 * ----------------------------------------------------------------------------
 */

bool
calmaParseElement(pnsrefs, pnpaths)
    int *pnsrefs, *pnpaths;
{
    static int node[] = { CALMA_ELFLAGS, CALMA_PLEX, CALMA_LAYER,
			  CALMA_NODETYPE, CALMA_XY, -1 };
    int nbytes, rtype;

    READRH(nbytes, rtype);
    if (nbytes < 0)
    {
	calmaReadError("Unexpected EOF.\n");
	return (FALSE);
    }

    switch (rtype)
    {
	case CALMA_AREF:
	case CALMA_SREF:
	    calmaElementSref();
	    (*pnsrefs)++;
	    break;
	case CALMA_BOUNDARY:	
	    calmaElementBoundary();
	    (*pnpaths)++;
	    break;
	case CALMA_BOX:
	    calmaElementBox();
	    (*pnpaths)++;
	    break;
	case CALMA_PATH:	
	    calmaElementPath();
	    (*pnpaths)++;
	    break;
	case CALMA_TEXT:	
	    calmaElementText();
	    break;
	case CALMA_NODE:
	    calmaReadError("NODE elements not supported: skipping.\n");
	    calmaSkipSet(node);
	    break;
	default:
	    UNREADRH(nbytes, rtype);
	    return (FALSE);
    }

    return (calmaSkipTo(CALMA_ENDEL));
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaElementSref --
 *
 * Process a structure reference (either CALMA_SREF or CALMA_AREF).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Consumes input.
 *	Adds a new cell use to the current def.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaElementSref()
{
    int nbytes, rtype, cols, rows, nref, n, i, savescale;
    int xlo, ylo, xhi, yhi, xsep, ysep;
    char *sname = NULL;
    bool isArray = FALSE;
    Transform trans, tinv;
    Point refarray[3], p;
    CellUse *use;
    CellDef *def;
    int gdsCopyPaintFunc();	/* Forward reference */
    /* Added by NP */
    char *useid = NULL, *arraystr = NULL;
    int propAttrType;

    /* Skip CALMA_ELFLAGS, CALMA_PLEX */
    calmaSkipSet(calmaElementIgnore);

    /* Read subcell name */
    if (!calmaReadStringRecord(CALMA_SNAME, &sname)) return;

    /*
     * Create a new cell use with this transform.  If the
     * cell being referenced doesn't exist, create it.
     * Don't give it a use-id; we will do that only after
     * we've placed all cells.
     */

    def = calmaLookCell(sname);
    if (!def && (CalmaPostOrder || CalmaFlattenUses))
    {
	/* Force the GDS parser to read the cell definition in
	 * post-order.  If cellname "sname" is not defined before
	 * it is used, then read the cell definition by jumping
	 * to its position in the stream file, reading that
	 * instance, and then returning to the current file
	 * position.  Note that this routine should be optimized
	 * for the likely case where many cells are defined at
	 * the end of the file; otherwise it will slow down the
	 * read process excessively for large GDS files by
	 * constantly moving from beginning to end of the file.
	 *
	 * Added by Nishit 8/16/2004
	 */

	off_t originalFilePos = calmaSetPosition(sname);
	if (!feof(calmaInputFile))
	{
	    HashTable OrigCalmaLayerHash;
	    int crsMultiplier = cifCurReadStyle->crs_multiplier;
	    char *currentSname = cifReadCellDef->cd_name;

	    OrigCalmaLayerHash = calmaLayerHash;
	    cifReadCellDef->cd_client = (ClientData)calmaExact();

	    /* Read cell definition "sname". */
	    calmaParseStructure();

	    /* Put things back to the way they were. */
	    fseek(calmaInputFile, originalFilePos, SEEK_SET);
	    cifReadCellDef = calmaLookCell(currentSname);
	    def = calmaLookCell(sname);
	    cifCurReadPlanes = (Plane **)cifReadCellDef->cd_client;
	    cifReadCellDef->cd_client = (ClientData)NULL;
	    calmaLayerHash = OrigCalmaLayerHash;
	    if (crsMultiplier != cifCurReadStyle->crs_multiplier)
	    {
		int scalen, scaled;
		if (crsMultiplier > cifCurReadStyle->crs_multiplier)
		{
		    scalen = crsMultiplier / cifCurReadStyle->crs_multiplier;
		    scaled = 1;
		}
		else
		{
		    scalen = 1;
		    scaled = cifCurReadStyle->crs_multiplier / crsMultiplier;
		}
		CIFScalePlanes(scalen, scaled, cifCurReadPlanes);
	    }
	}
	else
	{
	    TxPrintf("Cell definition %s does not exist!\n", sname);
	    fseek(calmaInputFile, originalFilePos, SEEK_SET);
	    def = calmaFindCell(sname, NULL);
	}
    }

    if (!def) def = calmaFindCell(sname, NULL);

    if (DBIsAncestor(def, cifReadCellDef))
    {
	calmaReadError("Cell %s is an ancestor of %s",
			def->cd_name, cifReadCellDef->cd_name);
	calmaReadError(" and can't be used as a subcell.\n");
	calmaReadError("(Use skipped)\n");
	return;
    }

    /* Read subcell transform */
    if (!calmaReadTransform(&trans, sname))
    {
	printf("Couldn't read transform.\n");
	return;
    }

    /* Get number of columns and rows if array */
    cols = rows = 0;  /* For half-smart compilers that complain otherwise. */
    READRH(nbytes, rtype);
    if (nbytes < 0) return;
    if (rtype == CALMA_COLROW)
    {
	isArray = TRUE;
	READI2(cols);
	READI2(rows);
	xlo = 0; xhi = cols - 1;
	ylo = 0; yhi = rows - 1;
	if (feof(calmaInputFile)) return;
	(void) calmaSkipBytes(nbytes - CALMAHEADERLENGTH - 4);
    }
    else
    {
	UNREADRH(nbytes, rtype);
    }

    /*
     * Read reference points.
     * For subcells, there will be a single reference point.
     * For arrays, there will be three; for their meanings, see below.
     */
    READRH(nbytes, rtype)
    if (nbytes < 0) return;
    if (rtype != CALMA_XY)
    {
	calmaUnexpected(CALMA_XY, rtype);
	return;
    }

    /* Length of remainder of record */
    nbytes -= CALMAHEADERLENGTH;

    /*
     * Read the reference points for the SREF/AREF.
     * Scale down by cifCurReadStyle->crs_scaleFactor, but complain
     * if they don't scale exactly.
     * Make sure we only read three points.
     */
    nref = nbytes / 8;
    if (nref > 3)
    {
	calmaReadError("Too many points (%d) in SREF/AREF\n", nref);
	nref = 3;
    }
    else if (nref < 1)
    {
	calmaReadError("Missing reference points in SREF/AREF (using 0,0)\n");
	refarray[0].p_x = refarray[0].p_y = 0;
	refarray[1].p_x = refarray[1].p_y = 0;
	refarray[2].p_x = refarray[2].p_y = 0;
    }

    /* If this is a cell reference, then we scale to magic coordinates
     * and place the cell in the magic database.  However, if this is
     * a cell to be flattened a la "gds flatten", then we keep the GDS
     * coordinates, and don't scale to the magic database.
     */

    if (def->cd_flags & CDFLATGDS)	/* Don't scale to magic coords! */
    {
	for (n = 0; n < nref; n++)
	{
	    calmaReadPoint(&refarray[n], 1);
	    if (feof(calmaInputFile))
		return;
	}
    }
    else
    {
	for (n = 0; n < nref; n++)
	{
	    savescale = cifCurReadStyle->crs_scaleFactor;
	    calmaReadPoint(&refarray[n], 1);
	    refarray[n].p_x = CIFScaleCoord(refarray[n].p_x, COORD_EXACT);
	    if (savescale != cifCurReadStyle->crs_scaleFactor)
	    {
		for (i = 0; i < n; i++)
		{
		    refarray[i].p_x *= (savescale / cifCurReadStyle->crs_scaleFactor);
		    refarray[i].p_y *= (savescale / cifCurReadStyle->crs_scaleFactor);
		}
		savescale = cifCurReadStyle->crs_scaleFactor;
	    }
	    refarray[n].p_y = CIFScaleCoord(refarray[n].p_y, COORD_EXACT);
	    if (savescale != cifCurReadStyle->crs_scaleFactor)
	    {
		for (i = 0; i < n; i++)
		{
		    refarray[i].p_x *= (savescale / cifCurReadStyle->crs_scaleFactor);
		    refarray[i].p_y *= (savescale / cifCurReadStyle->crs_scaleFactor);
		}
		refarray[n].p_x *= (savescale / cifCurReadStyle->crs_scaleFactor);
	    }

	    if (feof(calmaInputFile))
		return;
	}
    }

    /* Skip remainder */
    nbytes -= nref * 8;
    if (nbytes)
	(void) calmaSkipBytes(nbytes);

    /*
     * Figure out the inter-element spacing of array elements,
     * and also the translation part of the transform.
     * The first reference point for both SREFs and AREFs is the
     * translation of the use's or array's lower-left.
     */
    trans.t_c = refarray[0].p_x;
    trans.t_f = refarray[0].p_y;
    GeoInvertTrans(&trans, &tinv);
    if (isArray)
    {
	/*
	 * The remaining two points for an array are displaced from
	 * the first reference point by:
	 *    - the inter-column spacing times the number of columns,
	 *    - the inter-row spacing times the number of rows.
	 */
	xsep = ysep = 0;
	if (cols)
	{
	    GeoTransPoint(&tinv, &refarray[1], &p);
	    if (p.p_x % cols)
	    {
		n = (p.p_x + (cols+1)/2) / cols;
		calmaReadError("# cols doesn't divide displacement ref pt\n");
		calmaReadError("    %d / %d -> %d\n", p.p_x, cols, n);
		xsep = n;
	    }
	    else xsep = p.p_x / cols;
	}
	if (rows)
	{
	    GeoTransPoint(&tinv, &refarray[2], &p);
	    if (p.p_y % rows)
	    {
		n = (p.p_y + (rows+1)/2) / rows;
		calmaReadError("# rows doesn't divide displacement ref pt\n");
		calmaReadError("    %d / %d -> %d\n", p.p_y, rows, n);
		ysep = n;
	    }
	    ysep = p.p_y / rows;
	}
    }

    /* Added by NP --- parse PROPATTR and PROPVALUE record types */
    /* The PROPATTR types 98 and 99 are defined internally to	 */
    /* magic and are used to retain information that cannot be	 */
    /* saved to the GDS file in any other manner.		 */

    while (1)
    {
	READRH(nbytes, rtype);
	if (nbytes < 0) return;
	if (rtype == CALMA_PROPATTR)
	{
	    READI2(propAttrType);
	    if (propAttrType == CALMA_PROP_USENAME)
	    {
		if (!calmaReadStringRecord(CALMA_PROPVALUE, &useid))
		    return;
	    }
	    else if (propAttrType == CALMA_PROP_ARRAY_LIMITS)
	    {
		if (!calmaReadStringRecord(CALMA_PROPVALUE, &arraystr))
		    return;
		else if (arraystr)
		{
		    int xl, xh, yl, yh;
		    if (sscanf(arraystr, "%d_%d_%d_%d", &xl, &xh, &yl, &yh)
				!= 4)
			TxError("Malformed \"array\" property ignored: %s",
				arraystr);
		    else
		    {
			xlo = xl;
			ylo = yl;
			xhi = xh;
			yhi = yh;
		    }
		}
	    }
	}
	else
	{
	    UNREADRH(nbytes, rtype);
	    break;
	}
    }

    /*
     * Check for cells which have been marked for flattening
     */
    if (def->cd_flags & CDFLATGDS)
    {
	Plane **gdsplanes = (Plane **)def->cd_client;
	GDSCopyRec gdsCopyRec;
	int pNum;

	/* To do:  Deal with arrays by modifying trans and	*/
	/* looping over X and Y					*/

	for (pNum = 0; pNum < MAXCIFRLAYERS; pNum++)
	{
	    if (gdsplanes[pNum] != NULL)
	    {
		gdsCopyRec.plane = cifCurReadPlanes[pNum];
		if (isArray)
		{
		    Transform artrans;
		    int x, y;

		    gdsCopyRec.trans = &artrans;

		    for (x = 0; x < cols; x++)
			for (y = 0; y < rows; y++)
			{
			    GeoTransTranslate((x * xsep), (y * ysep), &trans,
					&artrans);
			    DBSrPaintArea((Tile *)NULL, gdsplanes[pNum],
					&TiPlaneRect, &DBAllButSpaceBits,
					gdsCopyPaintFunc, &gdsCopyRec);
			}
		}
		else
		{
		    gdsCopyRec.trans = &trans;
		    DBSrPaintArea((Tile *)NULL, gdsplanes[pNum], &TiPlaneRect,
			&DBAllButSpaceBits, gdsCopyPaintFunc, &gdsCopyRec);
		}
	    }
	}
    }
    else
    {
	use = DBCellNewUse(def, (useid) ? useid : (char *) NULL);
	if (isArray)
	    DBMakeArray(use, &GeoIdentityTransform, xlo, ylo, xhi, yhi, xsep, ysep);
	DBSetTrans(use, &trans);
	DBPlaceCell(use, cifReadCellDef);
    }

    /* Done with allocated variables */
    if (sname != NULL) freeMagic(sname);
    if (useid != NULL) freeMagic(useid);
    if (arraystr != NULL) freeMagic(arraystr);
}


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

int
gdsCopyPaintFunc(tile, gdsCopyRec)
    Tile *tile;
    GDSCopyRec *gdsCopyRec;
{
    int pNum;
    Rect sourceRect, targetRect;
    Transform *trans = gdsCopyRec->trans;
    Plane *plane = gdsCopyRec->plane;

    if (trans)
    {
	TiToRect(tile, &sourceRect);
	GeoTransRect(trans, &sourceRect, &targetRect);
    }
    else
	TiToRect(tile, &targetRect);

#ifdef NONMANHATTAN
    DBNMPaintPlane(plane, TiGetTypeExact(tile), &targetRect, CIFPaintTable,
		(PaintUndoInfo *)NULL);
#else
    DBPaintPlane(plane, &targetRect, CIFPaintTable, (PaintUndoInfo *)NULL);
#endif
    
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaFindCell --
 *
 * 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 name is 'name'.
 *
 * Side effects:
 *	A new CellDef may be created.
 *
 * ----------------------------------------------------------------------------
 */

CellDef *
calmaFindCell(name, was_called)
    char *name;		/* Name of desired cell */
    bool *was_called;	/* If this cell is in the hash table, then it
			 * was instanced before it was defined.  We
			 * need to know this so as to avoid flattening
			 * the cell if requested.
			 */

{
    HashEntry *h;
    CellDef *def;

    h = HashFind(&CifCellTable, name);
    if (HashGetValue(h) == 0)
    {
	def = DBCellLookDef(name);
	if (def == NULL)
	{
	    def = DBCellNewDef(name, (char *) NULL);

	    /*
	     * 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);
	if (was_called) *was_called = FALSE;
    }
    else
	if (was_called) *was_called = TRUE;
    return (CellDef *) HashGetValue(h);
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaLookCell --
 *
 * This procedure is like calmaFindCell above, but it will not
 * create a new subcell if the named cell does not already exist.
 *
 * Results:
 *	The return value is a pointer to the definition for the
 *	cell whose name is 'name', or NULL if cell 'name' does
 *	not exist.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

CellDef *
calmaLookCell(name)
    char *name;		/* Name of desired cell */
{
    HashEntry *h;

    h = HashLookOnly(&CifCellTable, name);
    if (h == NULL)
	return (CellDef *)NULL;
    else
	return (CellDef *)HashGetValue(h);
}

