/*
 * 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-8.0/calma/CalmaRdcl.c,v 1.5 2010/06/25 13:59:24 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;
int CalmaFlattenLimit = 10;

extern HashTable calmaDefInitHash;

/* forward declarations */
int  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;
    bool found = FALSE;

    originalPos = ftello(calmaInputFile);

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

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

        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);
	     freeMagic(strname);
	     return originalPos;
        }
	freeMagic(strname);
     }

     // Ran out of file.  It's possible that we were seeking ahead to a
     // definition that called something that was defined between it and
     // our previous position, so we will rewind the file and try again.
     // If that doesn't work, then the cell is not defined in the file.

     if (originalPos != 0)
     {
	rewind(calmaInputFile);
	calmaSetPosition(sname);
	return originalPos;
     }

     CalmaReadError("Cell \"%s\" is used but not defined in this file.\n", sname);

     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 unexpectedly.
		 * try to set the file pointer somewhere sane, but
		 * it will likely dump an error later on.
		 */
                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) && (rtype != CALMA_ENDLIB));

	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;
    HashEntry *he;
    int suffix;
    int mfactor;
    off_t filepos;
    bool was_called;
    bool was_initialized;
    bool predefined;
    bool do_flatten;
    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 */
    was_initialized = FALSE;
    predefined = FALSE;
    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	*/
	    /* (NOTE:  This is probably a bad policy in general)	*/
	    /* However, if post-ordering is set, then this cell was	*/
	    /* probably just read earlier, so don't gripe about it.	*/

	    if (!CalmaPostOrder)
	    {
		CalmaReadError("Cell \"%s\" was already defined in this file.\n",
				strname);
		CalmaReadError("Ignoring duplicate definition\n");
	    }
	    calmaNextCell();
	    return TRUE;
	}
	else
	{
	    char *newname;

	    CalmaReadError("Cell \"%s\" was already defined in this file.\n",
				strname);
	    newname = (char *)mallocMagic(strlen(strname) + 20);
	    for (suffix = 1; HashGetValue(he) != NULL; suffix++)
	    {
		(void) sprintf(newname, "%s_%d", strname, suffix);
		he = HashFind(&calmaDefInitHash, newname);
	    }
	    CalmaReadError("Giving this cell a new name: %s\n", newname);
	    freeMagic(strname);
	    strname = mallocMagic(strlen(newname) + 1);
	    strcpy(strname, newname);
	    freeMagic(newname);
	}
    }
    cifReadCellDef = calmaFindCell(strname, &was_called, &predefined);
    if (predefined == TRUE)
    {
	calmaNextCell();
	return TRUE;
    }
    DBCellClearDef(cifReadCellDef);
    DBCellSetAvail(cifReadCellDef);
    HashSetValue(he, cifReadCellDef);
    cifCurReadPlanes = cifSubcellPlanes;
    cifReadCellDef->cd_flags &= ~CDDEREFERENCE;

    /* 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));
    was_initialized = TRUE;

    /* Body of structure: a sequence of elements */
    osrefs = nsrefs = 0;
    npaths = 0;
    calmaNonManhattan = 0;
    while (calmaParseElement(filename, &nsrefs, &npaths))
    {
	if (SigInterruptPending)
	    goto done;
	if (nsrefs > osrefs && (nsrefs % 100) == 0)
	    TxPrintf("    %d uses\n", nsrefs);
	osrefs = nsrefs;
	calmaNonManhattan = 0;
    }

    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, "%"DLONG_PREFIX"d", (dlong) filepos);
	DBPropPut(cifReadCellDef, "GDS_START", (ClientData)fpcopy);

	fpcopy = (char *)mallocMagic(20);
	filepos = ftello(calmaInputFile);
	sprintf(fpcopy, "%"DLONG_PREFIX"d", (dlong) 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; */
    }

    /* Check if the cell name matches the pattern list of cells to flatten */

    do_flatten = FALSE;
    if ((CalmaFlattenUsesByName != NULL) && (!was_called))
    {
	int i = 0;
	char *pattern;

	while (TRUE)
	{
	    pattern = CalmaFlattenUsesByName[i];
	    if (pattern == NULL) break;
	    i++;

	    /* Check pattern against strname */
	    if (Match(pattern, strname))
	    {
		do_flatten = TRUE;
		break;
	    }
	}
    }
    if (CalmaFlattenUses && (!was_called) && (npaths < CalmaFlattenLimit)
                && (nsrefs == 0))
	do_flatten = TRUE;

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

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

    /*
     * 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 (do_flatten)
    {
	/* If CDFLATGDS is already set, may need to remove	*/
	/* existing planes and free memory.			*/

	if ((cifReadCellDef->cd_client != (ClientData)0) &&
		(cifReadCellDef->cd_flags & CDFLATGDS))
	{
	    Plane **cifplanes = (Plane **)cifReadCellDef->cd_client;
	    int pNum;

            for (pNum = 0; pNum < MAXCIFRLAYERS; pNum++)
            {
		if (cifplanes[pNum] != NULL)
                {
                    DBFreePaintPlane(cifplanes[pNum]);
                    TiFreePlane(cifplanes[pNum]);
                }
            }
            freeMagic((char *)cifReadCellDef->cd_client);
            cifReadCellDef->cd_client = (ClientData)0;
	}

	TxPrintf("Saving contents of cell %s\n", cifReadCellDef->cd_name);
	cifReadCellDef->cd_client = (ClientData) calmaExact();
	cifReadCellDef->cd_flags |= CDFLATGDS;
	cifReadCellDef->cd_flags &= ~CDFLATTENED;
    }
    else
    {
	/*
	 * Do the geometrical processing and paint this material back into
	 * the appropriate cell of the database.
	 */

	CIFPaintCurrent(FILE_CALMA);
    }

    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, or if "calma drcnocheck true"	*/
    /* has been issued.							*/

    if (!CalmaReadOnly && !CalmaNoDRCCheck)
	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:
    if (was_initialized == TRUE) 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(filename, pnsrefs, pnpaths)
    char *filename;
    int *pnsrefs, *pnpaths;
{
    static int node[] = { CALMA_ELFLAGS, CALMA_PLEX, CALMA_LAYER,
			  CALMA_NODETYPE, CALMA_XY, -1 };
    int nbytes, rtype, madeinst;

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

    switch (rtype)
    {
	case CALMA_AREF:
	case CALMA_SREF:
	    madeinst = calmaElementSref(filename);
	    if (madeinst >= 0)
		(*pnsrefs) += madeinst;
	    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:
 *	Returns 1 if an actual child instance was generated, 0 if
 *	only the child geometry was copied up, and -1 if an error
 * 	occurred.
 *
 * Side effects:
 *	Consumes input.
 *	Adds a new cell use to the current def.
 *
 * ----------------------------------------------------------------------------
 */

int
calmaElementSref(filename)
    char *filename;
{
    int nbytes, rtype, cols, rows, nref, n, i, savescale;
    int xlo, ylo, xhi, yhi, xsep, ysep;
    bool madeinst = FALSE;
    char *sname = NULL;
    bool isArray = FALSE;
    Transform trans, tinv;
    Point refarray[3], refunscaled[3], p;
    CellUse *use;
    CellDef *def;
    int gdsCopyPaintFunc();	/* Forward reference */
    int gdsHasUses();		/* 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 -1;

    /*
     * 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 || (CalmaFlattenUsesByName != NULL)))
    {
	/* 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;
	    Plane **savePlanes;

	    OrigCalmaLayerHash = calmaLayerHash;
	    savePlanes = calmaExact();

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

	    /* Put things back to the way they were. */
	    fseek(calmaInputFile, originalFilePos, SEEK_SET);
	    cifReadCellDef = calmaLookCell(currentSname);
	    def = calmaLookCell(sname);
	    cifCurReadPlanes = savePlanes;
	    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(scaled, scalen, cifCurReadPlanes);
	    }
	}
	else
	{
	    TxPrintf("Cell definition %s does not exist!\n", sname);
	    fseek(calmaInputFile, originalFilePos, SEEK_SET);
	    def = calmaFindCell(sname, NULL, NULL);
	    /* Cell flags set to "dereferenced" in case there is no	*/
	    /* definition in the GDS file.  If there is a definition	*/
	    /* made after the instance, then the flag will be cleared.	*/
	    def->cd_flags |= CDDEREFERENCE;
	}
    }

    if (!def) def = calmaFindCell(sname, NULL, 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 -1;
    }

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

    /* 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 -1;
    if (rtype == CALMA_COLROW)
    {
	isArray = TRUE;
	READI2(cols);
	READI2(rows);
	xlo = 0; xhi = cols - 1;
	ylo = 0; yhi = rows - 1;
	if (feof(calmaInputFile)) return -1;
	(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 -1;
    if (rtype != CALMA_XY)
    {
	calmaUnexpected(CALMA_XY, rtype);
	return -1;
    }

    /* 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.
     */

    for (n = 0; n < nref; n++)
    {
	savescale = cifCurReadStyle->crs_scaleFactor;
	calmaReadPoint(&refarray[n], 1);
	refunscaled[n] = refarray[n];	// Save for CDFLATGDS cells
	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 -1;
    }

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

    /* 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 -1;
	if (rtype == CALMA_PROPATTR)
	{
	    READI2(propAttrType);
	    if (propAttrType == CALMA_PROP_USENAME)
	    {
		if (!calmaReadStringRecord(CALMA_PROPVALUE, &useid))
		    return -1;
	    }
	    else if (propAttrType == CALMA_PROP_ARRAY_LIMITS)
	    {
		if (!calmaReadStringRecord(CALMA_PROPVALUE, &arraystr))
		    return -1;
		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;
	}
    }

    /* Transform manipulation.  Run through this twice if necessary.	*/
    /* The second time will use the unscaled transform to compute	*/
    /* the coordinates of saved geometry for a CD_FLATGDS cell.		*/

    for (i = 0; i < 2; i++)
    {
	if (i == 1)
	{
	    for (n = 0; n < nref; n++)
		refarray[n] = refunscaled[n]; 	// Restore for CDFLATGDS cells
	}

	/*
	 * 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;
	    }
	}

	/*
	 * Check for cells which have *not* been marked for flattening
	 */

	if (!(def->cd_flags & CDFLATGDS))
	{
	    use = DBCellNewUse(def, (useid) ? useid : (char *) NULL);
	    if (isArray)
		DBMakeArray(use, &GeoIdentityTransform, xlo, ylo, xhi, yhi, xsep, ysep);
	    DBSetTrans(use, &trans);
	    DBPlaceCell(use, cifReadCellDef);

	    break;	// No need to do 2nd loop
	}

	else if (i == 1)
	{
	    Plane **gdsplanes = (Plane **)def->cd_client;
	    GDSCopyRec gdsCopyRec;
	    int pNum;
	    bool hasUses;

	    // Mark cell as flattened (at least once)
	    def->cd_flags |= CDFLATTENED;

	    for (pNum = 0; pNum < MAXCIFRLAYERS; pNum++)
	    {
		if ((def->cd_client != (ClientData)0) && (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);
		    }
		}
	    }
	}

	/* If cell has children in addition to paint to be flattened,	*/
	/* then also generate an instance of the cell.			*/

	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);
	    madeinst = TRUE;
	}
    }

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

    return ((def->cd_flags & CDFLATGDS) ? (madeinst ? 1 : 0) : 1);
}


/* Callback function for determining if a cell has at least one subcell */

int
gdsHasUses(use, clientdata)
    CellUse *use;
    ClientData clientdata;
{
    return 1;
}

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

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

    dinfo = TiGetTypeExact(tile);

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

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

    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, predefined)
    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.
			 */
    bool *predefined;	/* If this cell was in memory before the GDS
			 * file was read, then this flag gets set.
			 */

{
    HashEntry *h;
    CellDef *def;

    h = HashFind(&CifCellTable, name);
    if (HashGetValue(h) == 0)
    {
	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);
	}
	else
	{
	    TxPrintf("Warning:  cell %s already existed before reading GDS!\n",
			name);
	    if (CalmaNoDuplicates)
	    {
		if (predefined) *predefined = TRUE;
	    	TxPrintf("Using pre-existing cell definition\n");
	    }
	}
	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);
}

