/*
 * CalmaWriteZ.c --
 *
 * Output of gzip-compressed Calma GDS-II stream format.
 * This is essentially a duplication of all functions in CalmaWrite.c that
 * write I/O to output, substituting zlib functions for the standard I/O
 * functions.  Note that since zlib can *read* an uncompressed file through
 * the same calls that it uses to write, then zlib is used for *all* GDS
 * file reads.  Only the GDS write functions have duplicate routines,
 * because writing an uncompressed file with zlib still writes a file in
 * gzip format, so to maintain the ability to write plain GDS files, a
 * set of routines is needed that is separate from the zlib routines.
 *
 *     *********************************************************************
 *     * 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/calma/CalmaWrite.c,v 1.8 2010/12/22 16:29:06 tim Exp $";
#endif  /* not lint */

#ifdef HAVE_ZLIB

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>	/* for random() */
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <sys/types.h>
#include <arpa/inet.h>	/* for htons() */
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include "utils/magic.h"
#include "utils/malloc.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/utils.h"
#include "utils/magic_zlib.h"
#include "utils/hash.h"
#include "database/database.h"
#include "database/databaseInt.h"
#include "utils/tech.h"
#include "cif/cif.h"
#include "cif/CIFint.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 "extract/extractInt.h"	/* for LabelList */
#include "utils/main.h"		/* for Path and CellLibPath */
#include "utils/stack.h"

/* C99 compat */
#include "utils/undo.h"
#include "calma/calma.h"

    /* External variables from CalmaWrite.c */
extern HashTable calmaLibHash;
extern HashTable calmaPrefixHash;
extern HashTable calmaUndefHash;
extern bool CalmaDoLibrary;
extern bool CalmaAllowUndefined;
extern bool CalmaAllowAbstract;
extern bool CalmaContactArrays;
extern bool CalmaAddendum;
extern bool CalmaMergeTiles;
extern bool CalmaDoLabels;
extern bool CalmaDoLower;
extern bool CalmaFlattenArrays;
extern time_t *CalmaDateStamp;
extern int calmaCellNum;
extern int calmaWriteScale;
extern int calmaPaintScale;
extern int calmaPaintLayerNumber;
extern int calmaPaintLayerType;

    /* External functions from CalmaWrite.c */
extern int calmaWriteInitFunc(CellDef *def, ClientData cdata);	/* UNUSED */

/* Structure used by calmaWritePaintFuncZ() and others */

typedef struct {
   gzFile f;		/* Compressed file stream for output	*/
   const Rect *area;	/* Clipping area, in GDS coordinates	*/
   int type;		/* Layer index				*/
} calmaOutputStructZ;

    /* Forward declarations */
extern int calmaWritePaintFuncZ(Tile *tile, TileType dinfo, calmaOutputStructZ *cos);
extern int calmaMergePaintFuncZ(Tile *tile, TileType dinfo, calmaOutputStructZ *cos);
extern int calmaWriteUseFuncZ(CellUse *use, gzFile f);
extern int calmaPaintLabelFuncZ(Tile *tile, TileType dinfo, calmaOutputStructZ *cos);
extern void calmaWriteContactsZ(gzFile f);
extern void calmaOutFuncZ(CellDef *def, gzFile f, const Rect *cliprect);
extern void calmaOutStructNameZ(int type, CellDef *def, gzFile f);
extern void calmaWriteLabelFuncZ(Label *lab, int ltype, int type, gzFile f);
extern void calmaOutHeaderZ(CellDef *rootDef, gzFile f);
extern void calmaOutDateZ(time_t t, gzFile f);
extern void calmaOutStringRecordZ(int type, char *str, gzFile f);
extern void calmaOut8Z(const char *str, gzFile f);
extern void calmaOutR8Z(double d, gzFile f);

/*--------------------------------------------------------------*/
/* Structures used by the tile merging algorithm                */
/*--------------------------------------------------------------*/

#define GDS_PENDING     0
#define GDS_UNPROCESSED CLIENTDEFAULT
#define GDS_PROCESSED   1

#define PUSHTILEZ(tp) \
    if (TiGetClient(tp) == GDS_UNPROCESSED) { \
        TiSetClientINT(tp, GDS_PENDING); \
        STACKPUSH((ClientData) (tp), SegStack); \
    }

/* -------------------------------------------------------------------- */

/*
 * Macros to output various pieces of Calma information.
 * These are macros for speed.
 */

/* -------------------------------------------------------------------- */

/*
 * calmaOutRHZ --
 *
 * Output a Calma record header.
 * This consists of a two-byte count of the number of bytes in the
 * record (including the two count bytes), a one-byte record type,
 * and a one-byte data type.
 */
#define	calmaOutRHZ(count, type, datatype, f) \
    { calmaOutI2Z(count, f); (void) gzputc(f, type); (void) gzputc(f, datatype); }

/*
 * calmaOutI2Z --
 *
 * Output a two-byte integer.
 * Calma byte order is the same as the network byte order used
 * by the various network library procedures.
 */
#define	calmaOutI2Z(n, f) \
    { \
	union { short u_s; char u_c[2]; } u; \
	u.u_s = htons(n); \
	(void) gzputc(f, u.u_c[0]); \
	(void) gzputc(f, u.u_c[1]); \
    }
/*
 * calmaOutI4Z --
 *
 * Output a four-byte integer.
 * Calma byte order is the same as the network byte order used
 * by the various network library procedures.
 */
#define calmaOutI4Z(n, f) \
    { \
	union { long u_i; char u_c[4]; } u; \
	u.u_i = htonl(n); \
	(void) gzputc(f, u.u_c[0]); \
	(void) gzputc(f, u.u_c[1]); \
	(void) gzputc(f, u.u_c[2]); \
	(void) gzputc(f, u.u_c[3]); \
    }

static const char calmaMapTableStrict[] =
{
      0,    0,    0,    0,    0,    0,    0,    0,      /* NUL - BEL */
      0,    0,    0,    0,    0,    0,    0,    0,      /* BS  - SI  */
      0,    0,    0,    0,    0,    0,    0,    0,      /* DLE - ETB */
      0,    0,    0,    0,    0,    0,    0,    0,      /* CAN - US  */
    '_',  '_',  '_',  '_',  '$',  '_',  '_',  '_',      /* SP  - '   */
    '_',  '_',  '_',  '_',  '_',  '_',  '_',  '_',      /* (   - /   */
    '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',      /* 0   - 7   */
    '8',  '9',  '_',  '_',  '_',  '_',  '_',  '_',      /* 8   - ?   */
    '_',  'A',  'B',  'C',  'D',  'E',  'F',  'G',      /* @   - G   */
    'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',      /* H   - O   */
    'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',      /* P   - W   */
    'X',  'Y',  'Z',  '_',  '_',  '_',  '_',  '_',      /* X   - _   */
    '_',  'a',  'b',  'c',  'd',  'e',  'f',  'g',      /* `   - g   */
    'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',      /* h   - o   */
    'p',  'q',  'r',  's',  't',  'u',  'v',  'w',      /* p   - w   */
    'x',  'y',  'z',  '_',  '_',  '_',  '_',  0,        /* x   - DEL */
};

static const char calmaMapTablePermissive[] =
{
      0,    0,    0,    0,    0,    0,    0,    0,      /* NUL - BEL */
      0,    0,    0,    0,    0,    0,    0,    0,      /* BS  - SI  */
      0,    0,    0,    0,    0,    0,    0,    0,      /* DLE - ETB */
      0,    0,    0,    0,    0,    0,    0,    0,      /* CAN - US  */
    ' ',  '!',  '"',  '#',  '$',  '&',  '%', '\'',      /* SP  - '   */
    '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',      /* (   - /   */
    '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',      /* 0   - 7   */
    '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',      /* 8   - ?   */
    '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',      /* @   - G   */
    'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',      /* H   - O   */
    'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',      /* P   - W   */
    'X',  'Y',  'Z',  '[', '\\',  ']',  '^',  '_',      /* X   - _   */
    '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',      /* `   - g   */
    'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',      /* h   - o   */
    'p',  'q',  'r',  's',  't',  'u',  'v',  'w',      /* p   - w   */
    'x',  'y',  'z',  '{',  '|',  '}',  '~',    0,      /* x   - DEL */
};

/*
 * ----------------------------------------------------------------------------
 *
 * CalmaWriteZ --
 *
 * Write out the entire tree rooted at the supplied CellDef in Calma
 * GDS-II stream format, to the specified file.
 *
 * Results:
 *	TRUE if the cell could be written successfully, FALSE otherwise.
 *
 * Side effects:
 *	Writes a file to disk.
 *	In the event of an error while writing out the cell,
 *	the external integer errno is set to the UNIX error
 *	encountered.
 *
 * Algorithm:
 *
 *	Calma names can be strings of up to CALMANAMELENGTH characters.
 *	Because general names won't map into Calma names, we use the
 *	original cell name only if it is legal Calma, and otherwise
 *	generate a unique numeric name for the cell.
 *
 *	We make a depth-first traversal of the entire design tree, outputting
 *	each cell to the Calma file.  If a given cell has not been read in
 *	when we visit it, we read it in ourselves.
 *
 *	No hierarchical design rule checking or bounding box computation
 *	occur during this traversal -- both are explicitly avoided.
 *
 * ----------------------------------------------------------------------------
 */

bool
CalmaWriteZ(
    CellDef *rootDef,	/* Pointer to CellDef to be written */
    gzFile f)		/* Open compressed output file */
{
    int oldCount = DBWFeedbackCount, problems, nerr;
    bool good;
    CellDef *err_def;
    CellUse dummy;
    HashEntry *he;
    HashSearch hs;

    /*
     * Do not attempt to write anything if a CIF/GDS output style
     * has not been specified in the technology file.
     */
    if (!CIFCurStyle)
    {
	TxError("No CIF/GDS output style set!\n");
	return FALSE;
    }

    HashInit(&calmaLibHash, 32, 0);
    HashInit(&calmaPrefixHash, 32, 0);
    HashInit(&calmaUndefHash, 32, 0);

    /*
     * Make sure that the entire hierarchy rooted at rootDef is
     * read into memory and that timestamp mismatches are resolved
     * (this is needed so that we know that bounding boxes are OK).
     */

    dummy.cu_def = rootDef;
    err_def = DBCellReadArea(&dummy, &rootDef->cd_bbox, !CalmaAllowUndefined);
    if (err_def != NULL)
    {
	TxError("Failure to read entire subtree of the cell.\n");
	TxError("Failed on cell %s.\n", err_def->cd_name);
	return FALSE;
    }

    DBFixMismatch();

    /*
     * Go through all cells currently having CellDefs in the
     * def symbol table and mark them with negative numbers
     * to show that they should be output, but haven't yet
     * been.
     */
    (void) DBCellSrDefs(0, calmaWriteInitFunc, (ClientData) NULL);
    rootDef->cd_client = (ClientData) -1;
    calmaCellNum = -2;

    /* Output the header, identifying this file */
    calmaOutHeaderZ(rootDef, f);

    /*
     * Write all contact cell definitions first
     */
    if (CalmaContactArrays) calmaWriteContactsZ(f);

    /*
     * We perform a post-order traversal of the tree rooted at 'rootDef',
     * to insure that each child cell is output before it is used.  The
     * root cell is output last.
     */
    good = (calmaProcessDefZ(rootDef, f, CalmaDoLibrary) == 0) ? TRUE : FALSE;

    /*
     * Check for any cells that were instanced in the output definition
     * (by dumping a GDS file from a read-only view) but were never
     * defined (because the dumped GDS contained undefined references).
     * If these are in the database but were not part of the tree of
     * rootDef, then output them at the end.
     */
    HashStartSearch(&hs);
    while ((he = HashNext(&calmaUndefHash, &hs)) != NULL)
    {
	char *refname = (char *)HashGetValue(he);
        if (refname && (refname[0] == '0'))
	{
	    CellDef *extraDef;

	    extraDef = DBCellLookDef((char *)he->h_key.h_name);
	    if (extraDef != NULL)
	    {
		if (calmaProcessDefZ(extraDef, f, FALSE) != 0)
		    good = FALSE;
	    }
	    else
	    	TxError("Error:  Cell %s is not defined in the output file!\n",
				refname + 1);
	}
    }

    /* Finish up by outputting the end-of-library marker */
    calmaOutRHZ(4, CALMA_ENDLIB, CALMA_NODATA, f);
    gzflush(f, Z_SYNC_FLUSH);
    gzerror(f, &nerr);
    if (nerr != 0) good = FALSE;

    /* See if any problems occurred */
    if ((problems = (DBWFeedbackCount - oldCount)))
	TxPrintf("%d problems occurred.  See feedback entries.\n", problems);

    /*
     * Destroy all contact cell definitions
     */
    if (CalmaContactArrays) calmaDelContacts();

    HashFreeKill(&calmaLibHash);
    HashKill(&calmaPrefixHash);
    HashFreeKill(&calmaUndefHash);
    return (good);
}


/*
 * ----------------------------------------------------------------------------
 *
 * calmaDumpStructureZ --
 *
 * Parse a structure (cell) from the GDS file.  Check the name against the
 * existing database and modify the name in case of a collision.  Then dump
 * the entire cell verbatim.  The cell gets prefixed with the name "prefix"
 * to prevent collisions with other unknown GDS files that may be dumped
 * verbatim.
 *
 * ----------------------------------------------------------------------------
 */

bool
calmaDumpStructureZ(
    CellDef *def,
    gzFile outf,
    HashTable *calmaDefHash,
    char *filename)
{
    int nbytes = -1, rtype = 0;
    char *strname = NULL, *newnameptr;
    HashEntry *he, *he2;
    CellDef *edef;
    char *prefix = NULL;

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

    if (rtype != CALMA_BGNSTR)
    {
	calmaOutRHZ(nbytes, rtype, CALMA_I2, outf);
        return (FALSE);
    }

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

    /* Output structure begin */
    calmaOutRHZ(28, CALMA_BGNSTR, CALMA_I2, outf);
    if (CalmaDateStamp != NULL)
    	calmaOutDateZ(*CalmaDateStamp, outf);
    else
    	calmaOutDateZ(def->cd_timestamp, outf);
    calmaOutDateZ(time((time_t *) 0), outf);

    /* Do a quick check of the calmaUndefHash table to see if this cell	*/
    /* was previously used in a GDS file that does not define it (a GDS	*/
    /* addendum library).						*/

    he = HashLookOnly(&calmaUndefHash, strname);
    if (he != NULL)
    {
	HashSearch hs;
	char *undefname = (char *)HashGetValue(he);

    	HashStartSearch(&hs);
    	while ((he2 = HashNext(&calmaLibHash, &hs)) != NULL)
	{
	    prefix = (char *)HashGetValue(he2);
	    if (!strncmp(prefix, undefname + 1, strlen(prefix)))
		break;
	}
	if (he2 == NULL)
	{
	    prefix = (char *)NULL;
	    TxError("Error:  Unreferenced cell %s prefix is unrecorded!\n",
			undefname);
	} 
	else
	{
	    /* Remove this entry from the hash table */
	    freeMagic(undefname);
	    HashRemove(&calmaUndefHash, strname);
	}
    }
    else
    {
    	/* Find the structure's unique prefix, in case structure calls	*/
    	/* subcells that are not yet defined.				*/

    	he2 = HashFind(&calmaLibHash, filename);
    	if (he2 == NULL)
	    TxError("Fatal error:  Library %s not recorded!\n", filename);
    	else
	    prefix = (char *)HashGetValue(he2);
    }

    /* Prefix structure name with def name, and output new structure name */
    he = HashFind(calmaDefHash, strname);
    if ((newnameptr = (char *)HashGetValue(he)) != NULL)
    {
	/* Structure is defined more than once */
	if (*newnameptr != '0')
	    TxError("Structure %s defined redundantly in GDS\n", strname);
	else
	    *newnameptr = '1';
	/* To be considered:  Should the structure be output more than once? */
	calmaOutStringRecordZ(CALMA_STRNAME, newnameptr + 1, outf);
    }
    else if (!strcmp(strname, def->cd_name))
    {
	/* This is the top level cell being defined.  Its name	*/
	/* does not get modified.				*/

	newnameptr = mallocMagic(strlen(strname) + 2);
	sprintf(newnameptr, "1%s", strname);
	calmaOutStringRecordZ(CALMA_STRNAME, newnameptr + 1, outf);
	HashSetValue(he, (char *)newnameptr);
    }
    else
    {
	/* Check if the cellname is in the magic cell database.	*/
	/* If so, check if that cell is an abstract view and	*/
	/* calls the same library.  If so, the name does not	*/
	/* get prefixed.  Otherwise, the cell is limited to the	*/
	/* GDS library being read, and so takes the prefix.	*/

	/* Modify the cellname by prefixing with "prefix", which is a	*/
	/* unique identifier for the library.				*/

	/* Check if the cell is defined in the database */
	edef = DBCellLookDef(strname);
	if (edef != NULL)
	{
	    bool isAbstract, isReadOnly;
	    char *chklibname, *filenamesubbed = NULL;

	    /* Is view abstract? */
	    DBPropGet(edef, "LEFview", &isAbstract);
	    chklibname = DBPropGetString(edef, "GDS_FILE", &isReadOnly);

	    if (isAbstract && isReadOnly)
	    {
		filenamesubbed = StrDup(NULL, filename);
		DBPathSubstitute(filename, filenamesubbed, edef);
	    }

	    /* Is the library name the same as the filename? */
	    if (isAbstract && isReadOnly && !strcmp(filenamesubbed, chklibname))
	    {
		/* Same library, so keep the cellname and mark the cell */
		/* as having been written to GDS.			*/

		newnameptr = mallocMagic(strlen(strname) + 2);
		sprintf(newnameptr, "1%s", strname);
		HashSetValue(he, (char *)newnameptr);
	    }
	    else
	    {
		/* Find the unique library prefix and prepend it to the cell name */

		if (prefix == NULL)
		    newnameptr = strname;   /* Should never happen */
		else
		{
		    newnameptr = mallocMagic(strlen(strname) + strlen(prefix) + 8);
		    sprintf(newnameptr, "1%s%s", prefix, strname);
		    HashSetValue(he, (char *)newnameptr);
		}
	    }
	    if (filenamesubbed) freeMagic(filenamesubbed);
	}
	else
	{
	    /* Find the unique library prefix and prepend it to the cell name */

	    if (prefix == NULL)
		newnameptr = strname;	    /* Should never happen */
	    else
	    {
		newnameptr = mallocMagic(strlen(strname) + strlen(prefix) + 8);
		sprintf(newnameptr, "1%s%s", prefix, strname);
		HashSetValue(he, (char *)newnameptr);
	    }
	}
	calmaOutStringRecordZ(CALMA_STRNAME, newnameptr + 1, outf);
    }
    freeMagic(strname);

    /* Read and output the structure until CALMA_ENDSTR, except */
    /* for handling any AREF or SREF names, which need name	*/
    /* checks.							*/

    while (1)
    {
	int datatype;

	READI2(nbytes);
	if (gzeof(calmaInputFile))
	{
	    /* Unexpected end-of-file */
	    gzseek(calmaInputFile, -(CALMAHEADERLENGTH), SEEK_END);
	    break;
	}
	rtype = gzgetc(calmaInputFile);
	datatype = gzgetc(calmaInputFile);
	switch (rtype) {
	    case CALMA_BGNSTR:
		UNREADRH(nbytes, rtype);
		return (TRUE);
	    case CALMA_ENDLIB:
		UNREADRH(nbytes, rtype);
		return (FALSE);

	    case CALMA_SNAME:
		UNREADRH(nbytes, rtype);
		if (!calmaReadStringRecord(CALMA_SNAME, &strname))
		    goto syntaxerror;

		he = HashFind(calmaDefHash, strname);
		newnameptr = (char *)HashGetValue(he);
		if (newnameptr != NULL)
		{
		    calmaOutStringRecordZ(CALMA_SNAME, newnameptr + 1, outf);
		}
		else
		{
		    TxError("Diagnostic:  %s is a forward reference?\n", strname);

		    /* Could be a forward reference, so do a rename in	*/
		    /* the same way used for structure definitions.	*/

		    newnameptr = (char *)mallocMagic(strlen(strname) +
				strlen(prefix) + 8);
		    sprintf(newnameptr, "0%s%s", prefix, strname);

		    edef = DBCellLookDef(newnameptr + 1);
		    if (edef != NULL)
			sprintf(newnameptr, "0%s%s[[0]]", prefix, strname);
		    HashSetValue(he, (char *)newnameptr);
		    calmaOutStringRecordZ(CALMA_SNAME, newnameptr + 1, outf);
		}
		break;

	    default:
		calmaOutRHZ(nbytes, rtype, datatype, outf);
		nbytes -= 4;

		/* Copy nbytes from input to output */
		while (nbytes-- > 0)
		{
		    int byte;
		    if ((byte = gzgetc(calmaInputFile)) < 0)
		    {
			TxError("End of file with %d bytes remaining to be read.\n",
				nbytes);
			while (nbytes-- > 0)
			    gzputc(outf, 0);	// zero-pad output
			return (FALSE);
		    }
		    else
			gzputc(outf, byte);
		}
		break;
	}
    }
    return (FALSE);

syntaxerror:
    /* Syntax error: skip to CALMA_ENDSTR */
    calmaSkipTo(CALMA_ENDSTR);
    return (FALSE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaFullDumpZ --
 *
 * Read in a GDS-II stream format library and dump its contents to
 * file "outf" verbatim except for cell references, which are renamed
 * if there is a conflict with a cell def in memory.
 *
 * Because the dump is inside a larger output, the header and trailer
 * are discarded.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaFullDumpZ(
    CellDef *def,
    gzFile fi,
    gzFile outf,
    char *filename)
{
    int version, rval;
    char *libname = NULL, *testlib, uniqlibname[4];
    char *sptr, *viewopts;
    bool isAbstract;
    HashTable calmaDefHash;
    HashSearch hs;
    HashEntry *he, *he2;

    static const int hdrSkip[] = { CALMA_FORMAT, CALMA_MASK, CALMA_ENDMASKS,
		CALMA_REFLIBS, CALMA_FONTS, CALMA_ATTRTABLE,
		CALMA_STYPTABLE, CALMA_GENERATIONS, -1 };
    static const int skipBeforeLib[] = { CALMA_LIBDIRSIZE, CALMA_SRFNAME,
		CALMA_LIBSECUR, -1 };

    HashInit(&calmaDefHash, 32, 0);

    calmaInputFile = fi;
    cifReadCellDef = def;

    /* Read and ignore the GDS-II header */

    if (!calmaReadI2Record(CALMA_HEADER, &version)) goto done;
    if (!calmaSkipExact(CALMA_BGNLIB)) goto done;
    calmaSkipSet(skipBeforeLib);
    if (!calmaReadStringRecord(CALMA_LIBNAME, &libname)) goto done;

    // CALMA_UNITS needs to be parsed to determine if units in the
    // input file are compatible with units being used in the output
    // file.
    if (calmaParseUnits() == FALSE)
    {
	TxError("Error:  Library %s has incompatible database units!\n", libname);
	return;
    }

    // Record the GDS library so it will not be processed again.
    he = HashFind(&calmaLibHash, filename);
    if ((char *)HashGetValue(he) != NULL)
    {
    	TxPrintf("Library %s has already been processed\n", libname);
	return;
    }

    /* If property LEFview is defined as "no_prefix" instead of "TRUE",
     * then do not create a unique prefix for subcells.  This is generally
     * ill-advised, but can be needed if a foundry runs specific DRC checks
     * on specific cell names, in which case adding a prefix can cause DRC
     * errors to appear.  It is then incumbent on the user to ensure that
     * names in the GDS file do not shadow any names in the database.
     */

    viewopts = DBPropGetString(def, "LEFview", &isAbstract);
    if ((!isAbstract) || (strcasecmp(viewopts, "no_prefix")))
    {
	/* Generate a SHORT name for this cell (else it is easy to run into the
	 * GDS 32-character cellname limit).  Save it in the hash record.  The
	 * chance of generating the same prefix for a library that has items
	 * with conflicting names is vanishingly small, but to be pedantic, store
	 * the prefix in a hash table and check to make sure that uniqueness is
	 * ensured.  NOTE:  The first character of a SPICE name cannot be a
	 * number.  Therefore the first character is alphabetical, and the second
	 * is alphanumeric.  There are only 936 possible combinations, but this
	 * is only meant to distinguish cells in large IP blocks of unknown
	 * origin, of which only a limited number would be expected.  Beware
	 * the implications for LVS, as the prefixed names from layout would
	 * need to be compared to un-prefixed names from another netlist.
	 */
	while (TRUE)
	{
	    rval = random() % 26;
	    rval = 'A' + rval;
	    uniqlibname[0] = (char)(rval & 127);
	    rval = random() % 36;
	    rval = (rval < 26) ? ('A' + rval) : ('0' + rval - 26);
	    uniqlibname[1] = (char)(rval & 127);
	    uniqlibname[2] = '_';
	    uniqlibname[3] = '\0';
	    he2 = HashLookOnly(&calmaPrefixHash, uniqlibname);
	    if (he2 == NULL)
	    {
		he2 = HashFind(&calmaPrefixHash, uniqlibname);
		break;
	    }
	}
	HashSetValue(he, StrDup(NULL, uniqlibname));
    }
    else
	HashSetValue(he, StrDup(NULL, ""));

    while (calmaDumpStructureZ(def, outf, &calmaDefHash, filename))
	if (SigInterruptPending)
	    goto done;
    calmaSkipExact(CALMA_ENDLIB);

done:

    /* Check that all references were resolved.  If not, then it is
     * probably because a library was an "addendum"-type library
     * referencing things in other libraries.  Move those cell
     * references to the calmaUndefHash before killing calmaDefHash.
     */

    HashStartSearch(&hs);
    while ((he = HashNext(&calmaDefHash, &hs)) != NULL)
    {
	char *refname = (char *)HashGetValue(he);
        if (refname[0] == '0')
	{
	    he2 = HashFind(&calmaUndefHash, (char *)he->h_key.h_name);
	    HashSetValue(he2, StrDup(NULL, refname));
	}
    }

    HashFreeKill(&calmaDefHash);
    if (libname != NULL) freeMagic(libname);
    return;
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaProcessUseZ --
 * calmaProcessDefZ --
 *
 * Main loop of Calma generation.  Performs a post-order, depth-first
 * traversal of the tree rooted at 'def'.  Only cells that have not
 * already been output are processed.
 *
 * The procedure calmaProcessDef() is called initially; calmaProcessUse()
 * is called internally by DBCellEnum().
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Causes Calma GDS-II stream-format to be output.
 *	Returns when the stack is empty.
 *
 * ----------------------------------------------------------------------------
 */

int
calmaProcessUseZ(
    CellUse *use,	/* Process use->cu_def */
    gzFile outf)	/* Stream file */
{
    return (calmaProcessDefZ(use->cu_def, outf, FALSE));
}

int
calmaProcessDefZ(
    CellDef *def,	/* Output this def's children, then the def itself */
    gzFile outf,		/* Stream file */
    bool do_library)	/* If TRUE, output only children of def, but not def */
{
    char *filename;
    int polyidx;
    bool isReadOnly, oldStyle, hasContent, isAbstract, hasGDSEnd, needHier;
    bool hierWrite, arrayWrite;
    HashEntry *he;

    /* Skip if already output */
    if ((int) CD2INT(def->cd_client) > 0)
	return (0);

    /* Assign it a (negative) number if it doesn't have one yet */
    if ((int) CD2INT(def->cd_client) == 0)
	def->cd_client = INT2CD(calmaCellNum--);

    /* Mark this cell */
    def->cd_client = INT2CD(- (int) CD2INT(def->cd_client));

    /* Read the cell in if it is not already available. */
    if ((def->cd_flags & CDAVAILABLE) == 0)
	if (!DBCellRead(def, TRUE, TRUE, NULL))
	    return (0);

    /*
     * Flag an error if attempting to write the default (UNNAMED) cell
     * into GDS.  This is not strictly an error but is almost certainly
     * not what the user intended.
     */

    if (!strcmp(def->cd_name, UNNAMED))
    {
	TxError("Error:  Cell has the default name \"%s\"!\n", UNNAMED);
    }

    /*
     * Check if this is a read-only file that is supposed to be copied
     * verbatim from input to output.  If so, do the direct copy.  If
     * not, or if there is any problem obtaining the original cell
     * definition, resort to writing out magic's version of the def,
     * and print a warning message.
     *
     * Treat the lack of a GDS_START property as an indication
     * that we should treat this cell like a reference-only
     * cell.  That is, the instance will be called but no
     * definition will appear in the output.
     */

    DBPropGet(def, "LEFview", &isAbstract);
    DBPropGet(def, "GDS_START", &hasContent);
    DBPropGet(def, "GDS_END", &hasGDSEnd);
    DBPropGet(def, "CIFhier", &needHier);
    filename = DBPropGetString(def, "GDS_FILE", &isReadOnly);

    /* When used with "calma addendum true", don't output the read-only	*/
    /* cells.  This makes the library incomplete and dependent on the	*/
    /* vendor libraries, so use with caution.				*/

    if (isReadOnly && hasContent && CalmaAddendum) return (0);

    if (isAbstract && !isReadOnly)
    {
	if (CalmaAllowAbstract)
	    TxError("Warning:  Writing abstract view of \"%s\" to GDS.\n",
			def->cd_name);
	else
	{
	    TxError("Error:  Cell \"%s\" is an abstract view;  cannot write GDS.\n",
			def->cd_name);
	    return 1;
	}
    }

    /*
     * Output the definitions for any of our descendants that have
     * not already been output.  Numbers are assigned to the subcells
     * as they are output.  If the cell will get a "full dump" (by
     * having GDS_START but no GDS_END), then do not output any subcells,
     * as they are expected to be in the referenced GDS file.
     */
    if (!hasContent || hasGDSEnd)
    {
	int result;

	if (needHier)
	{
	    hierWrite = CIFHierWriteDisable;
	    arrayWrite = CIFArrayWriteDisable;

	    CIFHierWriteDisable = FALSE;
	    CIFArrayWriteDisable = FALSE;
	}

	result = DBCellEnum(def, calmaProcessUseZ, (ClientData) outf);

	if (needHier)
	{
	    CIFHierWriteDisable = hierWrite;
	    CIFArrayWriteDisable = arrayWrite;
	}

	if (result != 0)
	    return 1;
    }

    /* Give some feedback to the user */
    TxPrintf("   Generating output for cell %s\n", def->cd_name);

    if (isReadOnly && hasContent)
    {
	char *buffer, *retfilename;
	size_t defsize, numbytes;
	z_off_t cellstart, cellend;
 	dlong cval;
	gzFile fi;

	/* Use PaOpen() so the paths searched are the same as were	*/
	/* searched to find the .mag file that indicated this GDS file.	*/

	fi = PaZOpen(filename, "r", "", Path, CellLibPath, &retfilename);

	/* Check if file may have been compressed */
	if (fi == NULL)
	    fi = PaZOpen(filename, "r", ".gz", Path, CellLibPath, &retfilename);

	if (fi == NULL)
	{
	    /* This is a rare error, but if the subcell is inside	*/
	    /* another vendor GDS, it would not normally be output.	*/

	    DBPropGet((def->cd_parents->cu_parent == NULL) ? def :
			def->cd_parents->cu_parent, "GDS_FILE", &isReadOnly);
	    if (isReadOnly)
	    {
		def->cd_flags |= CDVENDORGDS;
		return 0;	/* Ignore without raising an error */
	    }

	    TxError("Calma output error:  Can't find GDS file \"%s\" "
				"for vendor cell \"%s\".  It will not be output.\n",
				filename, def->cd_name);

	    if (CalmaAllowUndefined)
		return 0;
	    else
		return 1;
	}
	else if (isAbstract || (!hasGDSEnd))
	{
	    /* This is the trickiest part.  If the cell view is abstract then	*/
	    /* the cell view has no hierarchy, and there is no way to descend	*/
	    /* into the cell hierarchy and discover and write out all the	*/
	    /* dependencies.  The dependencies are in the file but not in the	*/
	    /* range GDS_START to GDS_END.  Furthermore, the dependencies have	*/
	    /* not been loaded so naming conflicts may exist.  So the file must	*/
	    /* be read end-to-end and parsed carefully.				*/

	    he = HashLookOnly(&calmaLibHash, retfilename);
	    if (he == NULL)
		calmaFullDumpZ(def, fi, outf, retfilename);

	    gzclose(fi);
	    def->cd_flags |= CDVENDORGDS;
	}
	else
	{
	    cval = DBPropGetDouble(def, "GDS_END", NULL);
	    cellend = (z_off_t)cval;
	    cval = DBPropGetDouble(def, "GDS_BEGIN", &oldStyle);
	    if (!oldStyle)
		cval = DBPropGetDouble(def, "GDS_START", NULL);

	    cellstart = (z_off_t)cval;

	    /* Sanity check:  The structure must be long enough to	*/
	    /* hold at least an ENDSTR record.				*/

	    if (cellend < cellstart + 4)
	    {
		TxError("Calma output error:  Bad vendor GDS file reference!\n");
		isReadOnly = FALSE;
	    }

	    if (isReadOnly && !oldStyle)
	    {
		/* GDS_START has been defined as the start of data after the	*/
		/* cell structure name.  A sanity check is wise, so search	*/
		/* backward from that position for the STRNAME record header.	*/
		/* Note that the length of the structure name in the file	*/
		/* cannot be assumed to be the length of the cell name in	*/
		/* magic, because the cell may have been renamed after the	*/
		/* GDS file was read.						*/

		z_off_t blockstart;
		size_t blocksize;
		int reclen;
		char *sptr = NULL;

		blockstart = cellstart - (z_off_t)CALMARECORDMAX;
		if (blockstart < 0) blockstart = (z_off_t)0;
		blocksize = (size_t)(cellstart - blockstart);

		gzseek(fi, blockstart, SEEK_SET);
		buffer = (char *)mallocMagic(blocksize + 1);
		numbytes = gzread(fi, buffer, sizeof(char) * blocksize);
		if (numbytes == blocksize)
		{
		    buffer[blocksize] = '\0';

		    /* Scan candidate record lengths from shortest to longest.	*/
		    /* A structure name contains only printable characters, so	*/
		    /* the first match found is the STRNAME record header.	*/

		    for (reclen = 6; reclen <= (int)blocksize; reclen += 2)
		    {
			char *tptr = buffer + blocksize - reclen;
			if (((unsigned char)tptr[0] == (unsigned char)(reclen >> 8))
				&& ((unsigned char)tptr[1] == (unsigned char)(reclen & 0xff))
				&& (tptr[2] == CALMA_STRNAME)
				&& (tptr[3] == CALMA_ASCII))
			{
			    sptr = tptr;
			    break;
			}
		    }
		    if (sptr == NULL)
		    {
			TxError("Calma output error:  Structure name not found"
				" ending at GDS file position %"DLONG_PREFIX"d\n",
				(dlong)cellstart);
			TxError("Calma output error:  Can't write cell from vendor GDS."
				"  Using magic's internal definition\n");
			isReadOnly = FALSE;
		    }
		    else if (strcmp(sptr + 4, def->cd_name))
		    {
			TxError("Calma output warning:  Structure definition has name"
				" %s but cell definition has name %s.\n",
				sptr + 4, def->cd_name);
			TxError("The structure definition will be given the cell name.\n");
		    }
		}
		else
		{
		    TxError("Calma output error:  Can't read cell from vendor GDS."
				"  Using magic's internal definition\n");
		    isReadOnly = FALSE;
		}
		freeMagic(buffer);
	    }
	    else if (isReadOnly)
	    {
		/* Old-style GDS_BEGIN points to the start of the structure	*/
		/* (BGNSTR record), which is copied verbatim including the	*/
		/* structure name;  check that the BGNSTR record is there.	*/

		char header[4];

		gzseek(fi, cellstart, SEEK_SET);
		numbytes = gzread(fi, header, sizeof(char) * 4);
		if ((numbytes != 4) || (header[2] != CALMA_BGNSTR)
				|| (header[3] != CALMA_I2))
		{
		    TxError("Calma output error:  Structure definition not found"
				" at GDS file position %"DLONG_PREFIX"d\n",
				(dlong)cellstart);
		    TxError("Calma output error:  Can't write cell from vendor GDS."
				"  Using magic's internal definition\n");
		    isReadOnly = FALSE;
		}
	    }

	    if (isReadOnly)
	    {
		/* Important note:  mallocMagic() is limited to size integer.	*/
		/* This will fail on a structure larger than ~2GB.		*/

		defsize = (size_t)(cellend - cellstart);
		buffer = (char *)mallocMagic(defsize);

		gzseek(fi, cellstart, SEEK_SET);
		numbytes = gzread(fi, buffer, sizeof(char) * (size_t)defsize);

		if (numbytes != defsize)
		{
		    TxError("Calma output error:  Can't read cell from vendor GDS."
				"  Using magic's internal definition\n");

		    /* Additional information as to why data did not match */
		    TxError("Size of data requested: %"DLONG_PREFIX"d", (dlong)defsize);
		    TxError("Length of data read: %"DLONG_PREFIX"d", (dlong)numbytes);
		    isReadOnly = FALSE;
		}
		else if (buffer[defsize - 4] != 0x00 ||
				buffer[defsize - 3] != 0x04 ||
				buffer[defsize - 2] != 0x07 ||
				buffer[defsize - 1] != 0x00)
		{
		    /* Sanity check:  buffer must end with a structure	*/
		    /* definition end (record 0x07).			*/

		    TxError("Calma output error:  Structure end definition not found"
				" at GDS file position %"DLONG_PREFIX"d\n", cellend);
		    TxError("Calma output error:  Can't write cell from vendor GDS."
				"  Using magic's internal definition\n");
		    isReadOnly = FALSE;
		}
		else
		{
		    /* All sanity checks have passed, so the structure header,	*/
		    /* name, and data can be written.  Nothing may be written	*/
		    /* to the output before this point, so that a failed check	*/
		    /* above falls back on the cell definition in magic without	*/
		    /* leaving a partial structure in the output.		*/

		    if (!oldStyle)
		    {
			/* Write our own header and string name, to ensure	*/
			/* that the magic cell name and GDS name match.		*/

			/* Output structure header */
			calmaOutRHZ(28, CALMA_BGNSTR, CALMA_I2, outf);
			if (CalmaDateStamp != NULL)
			    calmaOutDateZ(*CalmaDateStamp, outf);
			else
			    calmaOutDateZ(def->cd_timestamp, outf);
			calmaOutDateZ(time((time_t *) 0), outf);

			/* Name structure the same as the magic cellname */
			calmaOutStructNameZ(CALMA_STRNAME, def, outf);
		    }

		    numbytes = gzwrite(outf, buffer, sizeof(char) * (size_t)defsize);
		    if (numbytes != defsize)
		    {
			/* The structure header has already been written, so	*/
			/* it is not possible to fall back on the internal	*/
			/* cell definition;  the output is corrupt and the	*/
			/* error is fatal.					*/

			TxError("Calma output error:  Failed to write cell \"%s\""
				" from vendor GDS.  Output is invalid!\n",
				def->cd_name);
			freeMagic(buffer);
			gzclose(fi);
			return 1;
		    }
		}
		freeMagic(buffer);
	    }
	    gzclose(fi);

	    /* Mark the definition as vendor GDS so that magic doesn't	*/
	    /* try to generate subcell interaction or array interaction	*/
	    /* paint for it.						*/

	    def->cd_flags |= CDVENDORGDS;
	}
    }

    /* Quick check on "polygonXXXXX" cells---these are generated by the */
    /* "gds polygon subcell" option, and if the parent cell is a vendor */
    /* GDS file, then these cells do not actually exist in the layout   */
    /* and should not be output.                                        */

    if (isReadOnly == FALSE)
	if (!strncmp(def->cd_name, "polygon", 7))
	    if (sscanf(def->cd_name + 7, "%d", &polyidx) == 1)
		if (def->cd_parents->cu_parent != NULL)
		    DBPropGet(def->cd_parents->cu_parent, "GDS_FILE", &isReadOnly);

    /* Output this cell definition from the Magic database */
    if (!isReadOnly)
	if (!do_library)
	{
	    if (needHier)
	    {
		hierWrite = CIFHierWriteDisable;
		arrayWrite = CIFArrayWriteDisable;
	
		CIFHierWriteDisable = FALSE;
		CIFArrayWriteDisable = FALSE;
	    }

	    calmaOutFuncZ(def, outf, &TiPlaneRect);

	    if (needHier)
	    {
		CIFHierWriteDisable = hierWrite;
		CIFArrayWriteDisable = arrayWrite;
	    }
	}

    return 0;
}


/*
 * ----------------------------------------------------------------------------
 *
 * calmaOutFuncZ --
 *
 * Write out the definition for a single cell as a GDS-II stream format
 * structure.  We try to preserve the original cell's name if it is legal
 * in GDS-II; otherwise, we generate a unique name.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Appends to the open Calma output file.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaOutFuncZ(
    CellDef *def,	/* Pointer to cell def to be written */
    gzFile f,		/* Open output file */
    const Rect *cliprect)/* Area to clip to (used for contact cells),
			 * in CIF/GDS coordinates.
			 */
{
    Label *lab;
    CIFLayer *layer;
    Rect bigArea;
    int type;
    int dbunits;
    calmaOutputStructZ cos;
    bool propfound;
    PropertyRecord *proprec;
    char *propvalue;
    extern int compport(const void *one, const void *two);	/* Forward declaration */

    cos.f = f;
    cos.area = (cliprect == &TiPlaneRect) ? NULL : cliprect;
    cos.type = -1;

    /* Output structure begin */
    calmaOutRHZ(28, CALMA_BGNSTR, CALMA_I2, f);
    if (CalmaDateStamp != NULL)
    	calmaOutDateZ(*CalmaDateStamp, f);
    else
	calmaOutDateZ(def->cd_timestamp, f);
    calmaOutDateZ(time((time_t *) 0), f);

    /* Output structure name */
    calmaOutStructNameZ(CALMA_STRNAME, def, f);

    /* Since Calma database units are nanometers, multiply all units by 10,
     * modified by the scale multiplier.
     */

    dbunits = (CIFCurStyle->cs_flags & CWF_ANGSTROMS) ? 100 : 10;
    if ((dbunits % CIFCurStyle->cs_expander) == 0)
    {
	calmaWriteScale = CIFCurStyle->cs_scaleFactor * dbunits
		/ CIFCurStyle->cs_expander;
	calmaPaintScale = dbunits / CIFCurStyle->cs_expander;
    }
    else
    {
	TxError("Calma output error:  Output scale units are %2.1f nanometers.\n",
		(float)dbunits / (float)CIFCurStyle->cs_expander);
	TxError("Magic Calma output will be scaled incorrectly!\n");
	if ((dbunits == 10) && ((100 % CIFCurStyle->cs_expander) == 0))
	{
	    TxError("Please add \"units angstroms\" to the cifoutput section"
			" of the techfile.\n");
	}
	else
	{
	    TxError("Magic GDS output is limited to a minimum dimension of"
			" 1 angstrom.\n");
	}
	/* Set expander to 10 so output scales are not zero. */
	calmaWriteScale = CIFCurStyle->cs_scaleFactor;
	calmaPaintScale = 1;
    }

    /*
     * Output the calls that the child makes to its children.  For
     * arrays we output a single call, unlike CIF, since Calma
     * supports the notion of arrays.
     */
    (void) DBCellEnum(def, calmaWriteUseFuncZ, (ClientData) f);

    /* Output all the tiles associated with this cell; skip temporary layers */
    GEO_EXPAND(&def->cd_bbox, CIFCurStyle->cs_radius, &bigArea);

    /* Include any fixed bounding box as part of the area to process,	*/
    /* in case the fixed bounding box is larger than the geometry.	*/
    proprec = DBPropGet(def, "FIXED_BBOX", &propfound);
    if (propfound)
    {
	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];
	    GeoInclude(&bbox, &bigArea);
	}
    }

    CIFErrorDef = def;
    CIFGen(def, def, &bigArea, CIFPlanes, &DBAllTypeBits, TRUE, TRUE, FALSE,
		(ClientData)f);

    if (!CIFHierWriteDisable)
        CIFGenSubcells(def, &bigArea, CIFPlanes);
    if (!CIFArrayWriteDisable)
        CIFGenArrays(def, &bigArea, CIFPlanes);

    for (type = 0; type < CIFCurStyle->cs_nLayers; type++)
    {
	layer = CIFCurStyle->cs_layers[type];
	if (layer->cl_flags & CIF_TEMP) continue;
	if (!CalmaIsValidLayer(layer->cl_calmanum)) continue;
	cos.type = type;
	calmaPaintLayerNumber = layer->cl_calmanum;
	calmaPaintLayerType = layer->cl_calmatype;

	if (layer->cl_flags & CIF_LABEL)
	    DBSrPaintArea((Tile *) NULL, CIFPlanes[type],
		    cliprect, &CIFSolidBits, calmaPaintLabelFuncZ,
		    (ClientData) &cos);
	else
	    DBSrPaintArea((Tile *) NULL, CIFPlanes[type],
		    cliprect, &CIFSolidBits, (CalmaMergeTiles) ?
		    calmaMergePaintFuncZ : calmaWritePaintFuncZ,
		    (ClientData) &cos);
    }

    /* Output labels.  Do this in two passes, first for non-port labels	*/
    /* while finding the highest-numbered port.  Then output the port	*/
    /* labels (if any) in the order of the port index.			*/

    if (CalmaDoLabels)
    {
	int i, ltype, numports = 0;
	LabelList *ll = NULL, *newll;

	for (lab = def->cd_labels; lab; lab = lab->lab_next)
	{
	    if ((lab->lab_flags & PORT_DIR_MASK) == 0)
	    {
		ltype = CIFCurStyle->cs_labelLayer[lab->lab_type];
		type = ltype;
		calmaWriteLabelFuncZ(lab, ltype, type, f);
	    }
	    else
	    {
		newll = (LabelList *)mallocMagic(sizeof(LabelList));
		newll->ll_label = lab;
		newll->ll_attr = (unsigned int)lab->lab_port;
		newll->ll_next = ll;
		ll = newll;
		numports++;
	    }
	}
	if (ll != NULL)
	{
	    /* Turn linked list into an array, then run qsort on it	*/
	    /* to sort by port number.					*/

	    PortLabel *pllist = (PortLabel *)mallocMagic(numports * sizeof(PortLabel));
	    i = 0;
	    while (ll != NULL)
	    {
		pllist[i].pl_label = ll->ll_label;
		pllist[i].pl_port = (unsigned int)ll->ll_attr;
		free_magic1_t mm1 = freeMagic1_init();
		freeMagic1(&mm1, ll);
		ll = ll->ll_next;
		freeMagic1_end(&mm1);
		i++;
	    }

	    qsort(pllist, numports, sizeof(PortLabel), compport);

	    for (i = 0; i < numports; i++)
	    {
		lab = pllist[i].pl_label;
		ltype = CIFCurStyle->cs_portText[lab->lab_type];
		type = CIFCurStyle->cs_portLayer[lab->lab_type];
		if (type >= 0)
		    calmaWriteLabelFuncZ(lab, ltype, type, f);
	    }
	    freeMagic(pllist);
	}
    }

    /* End of structure */
    calmaOutRHZ(4, CALMA_ENDSTR, CALMA_NODATA, f);
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaWriteUseFuncZ --
 *
 * Filter function, called by DBCellEnum on behalf of calmaOutFunc above,
 * to write out each CellUse called by the CellDef being output.  If the
 * CellUse is an array, we output it as a single array instead of as
 * individual uses like CIF.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Appends to the open Calma output file.
 *
 * ----------------------------------------------------------------------------
 */

int
calmaWriteUseFuncZ(
    CellUse *use,
    gzFile f)
{
    /*
     * r90, r180, and r270 are Calma 8-byte real representations
     * of the angles 90, 180, and 270 degrees.  Because there are
     * only 4 possible values, it is faster to have them pre-computed
     * than to format with calmaOutR8Z().
     */
    static const unsigned char r90[] = { 0x42, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    static const unsigned char r180[] = { 0x42, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    static const unsigned char r270[] = { 0x43, 0x10, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00 };
    const unsigned char *whichangle;
    int x, y, topx, topy, rows, cols, xxlate, yxlate, hdrsize;
    int rectype, stransflags;
    Transform *t;
    bool isArray = FALSE;
    Point p, p2;

    topx = use->cu_xhi - use->cu_xlo;
    if (topx < 0) topx = -topx;
    topy = use->cu_yhi - use->cu_ylo;
    if (topy < 0) topy = -topy;

    /*
     * The following translates from the abcdef transforms that
     * we use internally to the rotation and mirroring specification
     * used in Calma stream files.  It only works because orientations
     * are orthogonal in magic, and no scaling is allowed in cell use
     * transforms.  Thus the elements a, b, d, and e always have one
     * of the following forms:
     *
     *		a  d
     *		b  e
     *
     * (counterclockwise rotations of 0, 90, 180, 270 degrees)
     *
     *	1  0	0  1	-1  0	 0 -1
     *	0  1   -1  0	 0 -1	 1  0
     *
     * (mirrored across the x-axis before counterclockwise rotation
     * by 0, 90, 180, 270 degrees):
     *
     *	1  0    0  1    -1  0    0 -1
     *	0 -1    1  0     0  1   -1  0
     *
     * Note that mirroring must be done if either a != e, or
     * a == 0 and b == d.
     *
     */
    t = &use->cu_transform;
    stransflags = 0;
    whichangle = (t->t_a == -1) ? r180 : (unsigned char *) NULL;
    if (t->t_a != t->t_e || (t->t_a == 0 && t->t_b == t->t_d))
    {
	stransflags |= CALMA_STRANS_UPSIDEDOWN;
	if (t->t_a == 0)
	{
	    if (t->t_b == 1) whichangle = r90;
	    else whichangle = r270;
	}
    }
    else if (t->t_a == 0)
    {
	if (t->t_b == -1) whichangle = r90;
	else whichangle = r270;
    }

    if (CalmaFlattenArrays)
    {
	for (x = 0; x <= topx; x++)
	{
	    for (y = 0; y <= topy; y++)
	    {
		/* Structure reference */
		calmaOutRHZ(4, CALMA_SREF, CALMA_NODATA, f);
		calmaOutStructNameZ(CALMA_SNAME, use->cu_def, f);

		/* Transformation flags */
		calmaOutRHZ(6, CALMA_STRANS, CALMA_BITARRAY, f);
		calmaOutI2Z(stransflags, f);

		/* Rotation if there is one */
		if (whichangle)
		{
		    calmaOutRHZ(12, CALMA_ANGLE, CALMA_R8, f);
		    calmaOut8Z((char *)whichangle, f);
		}

		/* Translation */
		xxlate = t->t_c + t->t_a*(use->cu_xsep)*x
				+ t->t_b*(use->cu_ysep)*y;
		yxlate = t->t_f + t->t_d*(use->cu_xsep)*x
				+ t->t_e*(use->cu_ysep)*y;
		xxlate *= calmaWriteScale;
		yxlate *= calmaWriteScale;
		calmaOutRHZ(12, CALMA_XY, CALMA_I4, f);
		calmaOutI4Z(xxlate, f);
		calmaOutI4Z(yxlate, f);

		/* End of element */
		calmaOutRHZ(4, CALMA_ENDEL, CALMA_NODATA, f);
	    }
	}
    }
    else
    {
	/* Is it an array? */
	isArray = (topx > 0 || topy > 0);
	rectype = isArray ? CALMA_AREF : CALMA_SREF;

	/* Structure reference */
	calmaOutRHZ(4, rectype, CALMA_NODATA, f);
	calmaOutStructNameZ(CALMA_SNAME, use->cu_def, f);

	/* Transformation flags */
	calmaOutRHZ(6, CALMA_STRANS, CALMA_BITARRAY, f);
	calmaOutI2Z(stransflags, f);

	/* Rotation if there is one */
	if (whichangle)
	{
	    calmaOutRHZ(12, CALMA_ANGLE, CALMA_R8, f);
	    calmaOut8Z((char *)whichangle, f);
	}

	/* If array, number of columns and rows in the array */
	if (isArray)
	{
	    calmaOutRHZ(8, CALMA_COLROW, CALMA_I2, f);
	    cols = topx + 1;
	    rows = topy + 1;
	    calmaOutI2Z(cols, f);
	    calmaOutI2Z(rows, f);
	}

	/* Translation */
	xxlate = t->t_c * calmaWriteScale;
	yxlate = t->t_f * calmaWriteScale;
	hdrsize = isArray ? 28 : 12;
	calmaOutRHZ(hdrsize, CALMA_XY, CALMA_I4, f);
	calmaOutI4Z(xxlate, f);
	calmaOutI4Z(yxlate, f);

	/* Array sizes if an array */
	if (isArray)
	{
	    /* Column reference point */
	    p.p_x = use->cu_xsep * cols;
	    p.p_y = 0;
	    GeoTransPoint(t, &p, &p2);
	    p2.p_x *= calmaWriteScale;
	    p2.p_y *= calmaWriteScale;
	    calmaOutI4Z(p2.p_x, f);
	    calmaOutI4Z(p2.p_y, f);

	    /* Row reference point */
	    p.p_x = 0;
	    p.p_y = use->cu_ysep * rows;
	    GeoTransPoint(t, &p, &p2);
	    p2.p_x *= calmaWriteScale;
	    p2.p_y *= calmaWriteScale;
	    calmaOutI4Z(p2.p_x, f);
	    calmaOutI4Z(p2.p_y, f);
	}

	/* By NP */
	/* Property attributes/value pairs. */
	/* Add a CellUse ID property, if the CellUse has a non-default name */
	/* (Modified 11/11/2022:  Do this always, not just the non-default case) */

	calmaOutRHZ(6, CALMA_PROPATTR, CALMA_I2, f);
	calmaOutI2Z(CALMA_PROP_USENAME_STD, f);
	calmaOutStringRecordZ(CALMA_PROPVALUE, use->cu_id, f);

	/* Add an array limits property, if the CellUse is an array and */
	/* limits of the array (xlo, ylo) are not zero (the default).	*/

	if ((use->cu_xlo != 0) || (use->cu_ylo != 0))
	{
	    char arraystr[128];
	    sprintf(arraystr, "%d_%d_%d_%d", use->cu_xlo, use->cu_xhi,
			use->cu_ylo, use->cu_yhi);
	    calmaOutRHZ(6, CALMA_PROPATTR, CALMA_I2, f);
	    calmaOutI2Z(CALMA_PROP_ARRAY_LIMITS, f);
	    calmaOutStringRecordZ(CALMA_PROPVALUE, arraystr, f);
	}

	/* End of element */
	calmaOutRHZ(4, CALMA_ENDEL, CALMA_NODATA, f);
    }

    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaOutStructName --
 *
 * Output the name of a cell def.
 * If the name is legal GDS-II, use it; otherwise, generate one
 * that is legal and unique.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the disk file.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaOutStructNameZ(
    int type,
    CellDef *def,
    gzFile f)
{
    char *defname;
    unsigned char c;
    char *cp;
    int calmanum;
    const char *table;

    if (CIFCurStyle->cs_flags & CWF_PERMISSIVE_LABELS)
    {
	table = calmaMapTablePermissive;
    } else {
	table = calmaMapTableStrict;
    }

    /* Is the def name a legal Calma name? */
    for (cp = def->cd_name; (c = (unsigned char) *cp); cp++)
    {
	if ((c > 127) || (table[c] == 0))
	    goto bad;
	else if ((unsigned char)table[c] != c)
	{
	    TxError("Warning: character \'%c\' changed to \'%c\' in"
		" name %s\n", (char)c, table[c], def->cd_name);
	}
	/* We really should ensure that the new name is unique. . . */
    }
    if ((!(CIFCurStyle->cs_flags & CWF_STRING_LIMIT)) ||
	    (cp <= def->cd_name + CALMANAMELENGTH))
    {
	/* Yes, it's legal: use it */
	defname = StrDup(NULL, def->cd_name);
    }
    else
    {
	/* Bad name: use XXXXXcalmaNum */
bad:
	calmanum = (int) CD2INT(def->cd_client);
	if (calmanum < 0) calmanum = -calmanum;
	defname = (char *)mallocMagic(32);
	(void) sprintf(defname, "XXXXX%d", calmanum);
	TxError("Warning: string in output unprintable; changed to \'%s\'\n",
		 defname);
    }

    calmaOutStringRecordZ(type, defname, f);
    freeMagic(defname);
}

/*
 * ----------------------------------------------------------------------------
 *
 * CalmaGenerateArrayZ --
 *
 *	This routine
 *
 * Results:
 *	TRUE on success, FALSE if no contact cell could be found.
 *
 * Side effects:
 *	Writes an AREF record to the GDS stream output.
 *
 * ----------------------------------------------------------------------------
 */

bool
CalmaGenerateArrayZ(
    gzFile f,		/* GDS output file */
    TileType type,	/* Magic tile type of contact */
    int llx,
    int lly,		/* Lower-left hand coordinate of the array
			 * (centered on contact cut)
			 */
    int pitch,		/* Pitch of the array elements */
    int cols,
    int rows)		/* Number of array elements in X and Y */
{
    CellDef *child;	/* Cell definition of the contact cell */
    int xxlate, yxlate;

    child = calmaGetContactCell(type, TRUE);
    if (child == NULL) return FALSE;

    /* Structure reference */
    calmaOutRHZ(4, CALMA_AREF, CALMA_NODATA, f);
    calmaOutStructNameZ(CALMA_SNAME, child, f);

    /* Transformation flags */
    calmaOutRHZ(6, CALMA_STRANS, CALMA_BITARRAY, f);
    calmaOutI2Z(0, f);

    /* Number of columns and rows in the array */
    calmaOutRHZ(8, CALMA_COLROW, CALMA_I2, f);
    calmaOutI2Z(cols, f);
    calmaOutI2Z(rows, f);

    /* Translation */
    xxlate = llx * calmaPaintScale;
    yxlate = lly * calmaPaintScale;
    calmaOutRHZ(28, CALMA_XY, CALMA_I4, f);
    calmaOutI4Z(xxlate, f);
    calmaOutI4Z(yxlate, f);

    /* Column reference point */
    calmaOutI4Z(xxlate + pitch * cols * calmaPaintScale, f);
    calmaOutI4Z(yxlate, f);

    /* Row reference point */
    calmaOutI4Z(xxlate, f);
    calmaOutI4Z(yxlate + pitch * rows * calmaPaintScale, f);

    /* End of AREF element */
    calmaOutRHZ(4, CALMA_ENDEL, CALMA_NODATA, f);

    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaWriteContactsZ --
 *
 *  This routine creates a new cellDef for each contact type and writes to
 *  the GDS output stream file. It is called before processing all cell
 *  definitions while writing GDS output.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes contact cell definition to the open Calma output file.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaWriteContactsZ(
    gzFile f)
{
    TileType type;
    TileTypeBitMask tMask, *rMask;
    CellDef *def, *cellDef;
    Rect area, cliprect;
    int halfwidth, halfsize;
    CIFOp *op;

    /* Turn off generation of contact arrays for the duration of this	*/
    /* subroutine, so that the contact definitions themselves will get	*/
    /* the proper contact cut drawn.  It is turned on again at the end	*/
    /* of the routine.  Note that this routine is not called unless	*/
    /* CalmaContactArrays is TRUE.					*/

    CalmaContactArrays = FALSE;

    DBEnumerateTypes(&tMask);

    /* Decompose stacking types */
    for (type = DBNumUserLayers; type < DBNumTypes; type++)
	if (TTMaskHasType(&tMask, type))
	{
	    rMask = DBResidueMask(type);
	    TTMaskSetMask(&tMask, rMask);
	}

    for (type = TT_SPACE + 1; type < DBNumUserLayers; type++)
    {
	/* We need to create cell array only for contact types */
	if (DBIsContact(type) && TTMaskHasType(&tMask, type))
	{
            /* Write definition of cell to GDS stream.	*/
	    /* Get cell definition for Tiletype type */
            def = calmaGetContactCell(type, FALSE);

            /* Get clip bounds, so that residue surround is	*/
	    /* minimum.  Note that these values are in CIF/GDS	*/
	    /* units, and the clipping rectangle passed to	*/
	    /* calmaOutFuncZ is also in CIF/GDS units.		*/

	    halfsize = CIFGetContactSize(type, NULL, NULL, NULL) >> 1;

            /* Get minimum width for layer by rounding halfsize	*/
	    /* to the nearest lambda value.			*/
            halfwidth = halfsize / CIFCurStyle->cs_scaleFactor;
	    if ((halfsize % CIFCurStyle->cs_scaleFactor) != 0)
		halfwidth++;

            area.r_xbot = area.r_ybot = -halfwidth;
            area.r_xtop = area.r_ytop = halfwidth;
       	    UndoDisable();
       	    DBPaint(def, &area, type);
       	    DBReComputeBbox(def);
    	    TTMaskSetType(&def->cd_types, type);

	    /* Clip output to the bounds of "cliprect"	*/
	    cliprect.r_xbot = cliprect.r_ybot = -halfsize;
	    cliprect.r_xtop = cliprect.r_ytop = halfsize;

            calmaOutFuncZ(def, f, &cliprect);
            UndoEnable();
	}
    }
    CalmaContactArrays = TRUE;
}

/*
 * ----------------------------------------------------------------------------
 * Process a LinkedBoundary list into a polygon and generate GDS output.
 * Free the linked list when done.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Output, memory deallocation.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaProcessBoundaryZ(
    BoundaryTop *blist,
    calmaOutputStructZ *cos)
{
    gzFile f = cos->f;
    LinkedBoundary *listtop, *lbref, *lbstop, *lbfree;
    BoundaryTop *bounds;
    int sval;
    int chkcount;	/* diagnostic */

    for (bounds = blist; bounds != NULL; bounds = bounds->bt_next)
    {
	/* Boundary */
	calmaOutRHZ(4, CALMA_BOUNDARY, CALMA_NODATA, f);

	/* Layer */
	calmaOutRHZ(6, CALMA_LAYER, CALMA_I2, f);
	calmaOutI2Z(calmaPaintLayerNumber, f);

	/* Data type */
	calmaOutRHZ(6, CALMA_DATATYPE, CALMA_I2, f);
	calmaOutI2Z(calmaPaintLayerType, f);

	/* Record length = ((#points + 1) * 2 values * 4 bytes) + 4 bytes header */
	calmaOutRHZ(4 + (bounds->bt_points + 1) * 8, CALMA_XY, CALMA_I4, f);

	/* Coordinates (repeat 1st point) */

	listtop = bounds->bt_first;
	lbstop = NULL;
	chkcount = 0;
	for (lbref = listtop; lbref != lbstop; lbref = lbref->lb_next)
	{
	    lbstop = listtop;
	    calmaOutI4Z(lbref->lb_start.p_x * calmaPaintScale, f);
	    calmaOutI4Z(lbref->lb_start.p_y * calmaPaintScale, f);
	    chkcount++;
	}
	if (listtop != NULL)
	{
	    calmaOutI4Z(listtop->lb_start.p_x * calmaPaintScale, f);
	    calmaOutI4Z(listtop->lb_start.p_y * calmaPaintScale, f);
	}

	if (chkcount != bounds->bt_points)
	    TxError("Points recorded=%d;  Points written=%d\n",
			bounds->bt_points, chkcount);

	/* End of element */
	calmaOutRHZ(4, CALMA_ENDEL, CALMA_NODATA, f);

	/* Free the LinkedBoundary list */

	{
	    free_magic1_t mm1 = freeMagic1_init();
	    lbref = listtop;
	    while (lbref->lb_next != listtop)
	    {
		freeMagic1(&mm1, lbref);
		lbref = lbref->lb_next;
	    }
	    freeMagic1(&mm1, lbref);
	    freeMagic1_end(&mm1);
	}
    }

    /* Free the BoundaryTop list */

    {
	free_magic1_t mm1 = freeMagic1_init();
	for (bounds = blist; bounds != NULL; bounds = bounds->bt_next)
	    freeMagic1(&mm1, bounds);
	freeMagic1_end(&mm1);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaMergePaintFuncZ --
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the gzFile cos->f
 *
 * ----------------------------------------------------------------------------
 */

int
calmaMergePaintFuncZ(
    Tile *tile,			/* Tile to be written out. */
    TileType dinfo,		/* Split tile information (unused) */
    calmaOutputStructZ *cos)	/* Information needed by algorithm */
{
    gzFile f = cos->f;
    const Rect *clipArea = cos->area;
    Tile *t, *tp;
    TileType ttype;
    int i, llx, lly, urx, ury, intedges, num_points, split_type;
    bool is_ext;
    static Stack *SegStack = (Stack *)NULL;

    static LinkedBoundary *edge;
    LinkedBoundary *lb;
    BoundaryTop *bounds = NULL;

    /* Quick check for tiles that have already been processed */
    if (TiGetClientINT(tile) == GDS_PROCESSED) return 0;

    if (SegStack == (Stack *)NULL)
	SegStack = StackNew(64);

    PUSHTILEZ(tile);
    while (!StackEmpty(SegStack))
    {
	t = (Tile *) STACKPOP(SegStack);
	if (TiGetClientINT(t) != GDS_PENDING) continue;
	TiSetClientINT(t, GDS_PROCESSED);

	split_type = -1;
	if (IsSplit(t))
	{
	    /* If we use TT_SIDE, then we need to set it when the	*/
	    /* tile is pushed.  Since these are one-or-zero mask layers	*/
	    /* I assume it is okay to just check which side is TT_SPACE	*/

	    split_type = SplitDirection(t);
	    if (TiGetLeftType(t) == TT_SPACE) split_type |= 2;
	    num_points = 2;
	    if (edge != NULL)
	    {
		/* Remove one point from the edge record for rectangles */
		/* and relink the last entry back to the new head.	*/

		lb = edge;
		while (lb->lb_next != edge) lb = lb->lb_next;
		lb->lb_next = edge->lb_next;
		free_magic1_t mm1 = freeMagic1_init();
		freeMagic1(&mm1, edge);
		edge = edge->lb_next;
		freeMagic1_end(&mm1);
	    }
	}
	else
	    num_points = 3;

	/* Create a new linked boundary structure with 4 unknown edges.	*/
	/* This structure is reused when we encounter isolated tiles,	*/
	/* so we avoid unnecessary overhead in the case of, for		*/
	/* example, large contact cut arrays.				*/

	if (edge == NULL)
	{
	    edge = (LinkedBoundary *)mallocMagic(sizeof(LinkedBoundary));
	    lb = edge;

	    for (i = 0; i < num_points; i++)
	    {
		lb->lb_type = LB_INIT;
		lb->lb_next = (LinkedBoundary *)mallocMagic(sizeof(LinkedBoundary));
		lb = lb->lb_next;
	    }
	    lb->lb_type = LB_INIT;
	    lb->lb_next = edge;
	}

	lb = edge;
	llx = LEFT(t);
	lly = BOTTOM(t);
	urx = RIGHT(t);
	ury = TOP(t);
	intedges = 0;

	/* Initialize the "edge" record with the corner points of the	*/
	/* tile.							*/

	if (IsSplit(t))
	{
	    switch (split_type)
	    {
		case 0x0:
		    lb->lb_start.p_x = urx;
		    lb->lb_start.p_y = ury;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    lb->lb_start.p_x = llx;
		    lb->lb_start.p_y = ury;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    lb->lb_start.p_x = llx;
		    lb->lb_start.p_y = lly;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    break;
		case 0x1:
		    lb->lb_start.p_x = llx;
		    lb->lb_start.p_y = ury;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    lb->lb_start.p_x = llx;
		    lb->lb_start.p_y = lly;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    lb->lb_start.p_x = urx;
		    lb->lb_start.p_y = lly;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    break;
		case 0x2:
		    lb->lb_start.p_x = urx;
		    lb->lb_start.p_y = ury;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    lb->lb_start.p_x = llx;
		    lb->lb_start.p_y = lly;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    lb->lb_start.p_x = urx;
		    lb->lb_start.p_y = lly;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    break;
		case 0x3:
		    lb->lb_start.p_x = urx;
		    lb->lb_start.p_y = ury;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    lb->lb_start.p_x = llx;
		    lb->lb_start.p_y = ury;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    lb->lb_start.p_x = urx;
		    lb->lb_start.p_y = lly;
		    lb->lb_type = LB_INIT;
		    lb = lb->lb_next;
		    break;
	    }
	    num_points = 1;
	}
	else
	{
	    lb->lb_start.p_x = urx;
	    lb->lb_start.p_y = ury;
	    lb->lb_type = LB_INIT;
	    lb = lb->lb_next;
	    lb->lb_start.p_x = llx;
	    lb->lb_start.p_y = ury;
	    lb->lb_type = LB_INIT;
	    lb = lb->lb_next;
	    lb->lb_start.p_x = llx;
	    lb->lb_start.p_y = lly;
	    lb->lb_type = LB_INIT;
	    lb = lb->lb_next;
	    lb->lb_start.p_x = urx;
	    lb->lb_start.p_y = lly;
	    lb->lb_type = LB_INIT;
	    lb = lb->lb_next;

	    num_points = 0;
	}
	if (split_type == 0x1) goto left_search;

	/* Search the tile boundary for connected and unconnected tiles.	*/
	/* Generate segments in a counterclockwise cycle.			*/

	if (split_type == 0x2)
	{
	    intedges += calmaAddSegment(&lb, TRUE, RIGHT(t), TOP(t),
			LEFT(t), BOTTOM(t));
	    goto bottom_search;
	}

	/* Search top */

	ttype = TiGetTopType(t);
	for (tp = RT(t); RIGHT(tp) > LEFT(t); tp = BL(tp), num_points++)
	{
	    is_ext = (TiGetBottomType(tp) != ttype) ? TRUE : FALSE;
	    intedges += calmaAddSegment(&lb, is_ext,
			MIN(RIGHT(t), RIGHT(tp)), TOP(t),
			MAX(LEFT(t), LEFT(tp)), TOP(t));
	    if (!is_ext) PUSHTILEZ(tp);
	}

	if (split_type == 0x3)
	{
	    intedges += calmaAddSegment(&lb, TRUE, LEFT(t), TOP(t),
			RIGHT(t), BOTTOM(t));
	    goto right_search;
	}

	/* Search left */

left_search:
	ttype = TiGetLeftType(t);
	for (tp = BL(t); BOTTOM(tp) < TOP(t); tp = RT(tp), num_points++)
	{
	    is_ext = (TiGetRightType(tp) != ttype) ? TRUE : FALSE;
	    intedges += calmaAddSegment(&lb, is_ext,
			LEFT(t), MIN(TOP(t), TOP(tp)),
			LEFT(t), MAX(BOTTOM(t), BOTTOM(tp)));
	    if (!is_ext) PUSHTILEZ(tp);
	}

	if (split_type == 0x0)
	{
	    intedges += calmaAddSegment(&lb, TRUE, LEFT(t), BOTTOM(t),
			RIGHT(t), TOP(t));
	    goto done_searches;
	}

	/* Search bottom */

bottom_search:
	ttype = TiGetBottomType(t);
	for (tp = LB(t); LEFT(tp) < RIGHT(t); tp = TR(tp), num_points++)
	{
	    is_ext = (TiGetTopType(tp) != ttype) ? TRUE : FALSE;
	    intedges += calmaAddSegment(&lb, is_ext,
			MAX(LEFT(t), LEFT(tp)), BOTTOM(t),
			MIN(RIGHT(t), RIGHT(tp)), BOTTOM(t));
	    if (!is_ext) PUSHTILEZ(tp);
	}

	if (split_type == 0x1)
	{
	    intedges += calmaAddSegment(&lb, TRUE, RIGHT(t), BOTTOM(t),
			LEFT(t), TOP(t));
	    goto done_searches;
	}
	/* Search right */

right_search:
	ttype = TiGetRightType(t);
	for (tp = TR(t); TOP(tp) > BOTTOM(t); tp = LB(tp), num_points++)
	{
	    is_ext = (TiGetLeftType(tp) != ttype) ? TRUE : FALSE;
	    intedges += calmaAddSegment(&lb, is_ext,
			RIGHT(t), MAX(BOTTOM(t), BOTTOM(tp)),
			RIGHT(t), MIN(TOP(t), TOP(tp)));
	    if (!is_ext) PUSHTILEZ(tp);
	}

	/* If tile is isolated, process it now and we're done */

done_searches:
	if (intedges == 0)
	{
	    calmaWritePaintFuncZ(t,
			(split_type & 2) ? (TileType)TT_SIDE : (TileType)0,
			cos);

	    /* Although calmaWritePaintFunc is called only on isolated	*/
	    /* tiles, we may have expanded it.  This could use a LOT of	*/
	    /* optimizing.  1) remove colinear points in calmaAddSegment */
	    /* when both subsegments are external paths, and 2) here,	*/
	    /* take the shortest path to making "edge" exactly 4 points.*/
	    /* Note that in non-Manhattan mode, num_points may be 3.	*/

	    if (num_points != 4)
	    {
		free_magic1_t mm1 = freeMagic1_init();
		for (i = 0; i < num_points; i++)
		{
		    freeMagic1(&mm1, edge);
		    edge = edge->lb_next;
		}
		freeMagic1_end(&mm1);
		edge = NULL;
	    }
	    if (!StackEmpty(SegStack))
		TxError("ERROR:  Segment stack is supposed to be empty!\n");
	    else
		return 0;
	}
	else
	{
	    /* Merge boundary into existing record */

	    calmaMergeSegments(edge, &bounds, num_points);
	    edge = NULL;
	}
    }

    /* Remove any degenerate points */
    calmaRemoveDegenerate(bounds);

    /* Remove any colinear points */
    calmaRemoveColinear(bounds);

    /* Output the boundary records */
    calmaProcessBoundaryZ(bounds, cos);

    return 0;	/* Keep the search alive. . . */
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaWritePaintFuncZ --
 *
 * Filter function used to write out a single paint tile.
 *
 *			**** NOTE ****
 * There are loads of Calma systems out in the world that
 * don't understand CALMA_BOX, so we output CALMA_BOUNDARY
 * even though CALMA_BOX is more appropriate.  Bletch.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the gzFile 'f'
 *
 * ----------------------------------------------------------------------------
 */

int
calmaWritePaintFuncZ(
    Tile *tile,			/* Tile to be written out. */
    TileType dinfo,		/* Split tile information */
    calmaOutputStructZ *cos)	/* File for output and clipping area */
{
    gzFile f = cos->f;
    const Rect *clipArea = cos->area;
    Rect r, r2;

    TiToRect(tile, &r);
    if (clipArea != NULL)
	GeoClip(&r, clipArea);

    r.r_xbot *= calmaPaintScale;
    r.r_ybot *= calmaPaintScale;
    r.r_xtop *= calmaPaintScale;
    r.r_ytop *= calmaPaintScale;

    /* Boundary */
    calmaOutRHZ(4, CALMA_BOUNDARY, CALMA_NODATA, f);

    /* Layer */
    calmaOutRHZ(6, CALMA_LAYER, CALMA_I2, f);
    calmaOutI2Z(calmaPaintLayerNumber, f);

    /* Data type */
    calmaOutRHZ(6, CALMA_DATATYPE, CALMA_I2, f);
    calmaOutI2Z(calmaPaintLayerType, f);

    /* The inefficient use of CALMA_BOUNDARY for rectangles actually	*/
    /* makes it easy to implement triangles, since they must be defined */
    /* by CALMA_BOUNDARY.						*/

    if (IsSplit(tile))
    {
	/* Coordinates */
	calmaOutRHZ(36, CALMA_XY, CALMA_I4, f);

	switch (((dinfo & TT_SIDE) ? 2 : 0) | SplitDirection(tile))
	{
	    case 0x0:
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ybot, f);
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ytop, f);
		calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ytop, f);
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ybot, f);
		break;
	    case 0x1:
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ytop, f);
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ybot, f);
		calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ybot, f);
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ytop, f);
		break;
	    case 0x2:
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ybot, f);
		calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ybot, f);
		calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ytop, f);
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ybot, f);
		break;
	    case 0x3:
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ytop, f);
		calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ytop, f);
		calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ybot, f);
		calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ytop, f);
		break;
	}
    }
    else
    {
	/* Coordinates */
	calmaOutRHZ(44, CALMA_XY, CALMA_I4, f);
	calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ybot, f);
	calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ybot, f);
	calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ytop, f);
	calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ytop, f);
	calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ybot, f);
    }

    /* End of element */
    calmaOutRHZ(4, CALMA_ENDEL, CALMA_NODATA, f);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaWriteLabelFuncZ --
 *
 * Output a single label to the compressed stream file 'f'.
 *
 * The CIF type to which this label is attached is 'type'; if this
 * is < 0 then the label is not output.
 *
 * Non-point labels are collapsed to point labels located at the center
 * of the original label.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the gzFile 'f'.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaWriteLabelFuncZ(
    Label *lab,	/* Label to output */
    int ltype,	/* CIF layer number to use for TEXT record */
    int type,	/* CIF layer number to use for BOUNDARY record,
		 * or -1 if not attached to a layer
		 */
    gzFile f)	/* Stream file */
{
    Point p;
    int calmanum, calmatype;

    if (ltype < 0)
	return;

    calmanum = CIFCurStyle->cs_layers[ltype]->cl_calmanum;
    if (!CalmaIsValidLayer(calmanum))
	return;

    calmaOutRHZ(4, CALMA_TEXT, CALMA_NODATA, f);

    calmaOutRHZ(6, CALMA_LAYER, CALMA_I2, f);
    calmaOutI2Z(calmanum, f);

    calmatype = CIFCurStyle->cs_layers[ltype]->cl_calmatype;
    calmaOutRHZ(6, CALMA_TEXTTYPE, CALMA_I2, f);
    calmaOutI2Z(calmatype, f);

    if (lab->lab_font >= 0)
    {
	unsigned short textpres = 0;

	/* A bit of a hack here.  Magic can have any number of fonts,	*/
	/* but GDS only allows four of them.  So we just crop the font	*/
	/* index to two bits.  We provide no other font information, so	*/
	/* this is highly implementation-dependent.  But it allows us	*/
	/* to retain font information when reading and writing our own	*/
	/* GDS files.							*/

	textpres = (lab->lab_font & 0x03) << 4;

	switch(lab->lab_just)
	{
	    case GEO_SOUTH:
		textpres |= 0x0001;
		break;
	    case GEO_SOUTHEAST:
		textpres |= 0x0000;
		break;
	    case GEO_EAST:
		textpres |= 0x0004;
		break;
	    case GEO_NORTHEAST:
		textpres |= 0x0008;
		break;
	    case GEO_NORTH:
		textpres |= 0x0009;
		break;
	    case GEO_NORTHWEST:
		textpres |= 0x000a;
		break;
	    case GEO_WEST:
		textpres |= 0x0006;
		break;
	    case GEO_SOUTHWEST:
		textpres |= 0x0002;
		break;
	    case GEO_CENTER:
		textpres |= 0x0005;
		break;
	}

	calmaOutRHZ(6, CALMA_PRESENTATION, CALMA_BITARRAY, f);
	calmaOutI2Z(textpres, f);

	calmaOutRHZ(6, CALMA_STRANS, CALMA_BITARRAY, f);
	calmaOutI2Z(0, f);	/* Any need for these bits? */

	calmaOutRHZ(12, CALMA_MAG, CALMA_R8, f);
	calmaOutR8Z(((double)lab->lab_size / 800)
		* (double)CIFCurStyle->cs_scaleFactor
		/ (double)CIFCurStyle->cs_expander, f);

	if (lab->lab_rotate != 0)
	{
	    calmaOutRHZ(12, CALMA_ANGLE, CALMA_R8, f);
	    calmaOutR8Z((double)lab->lab_rotate, f);
	}
    }

    p.p_x = (lab->lab_rect.r_xbot + lab->lab_rect.r_xtop) * calmaWriteScale / 2;
    p.p_y = (lab->lab_rect.r_ybot + lab->lab_rect.r_ytop) * calmaWriteScale / 2;
    calmaOutRHZ(12, CALMA_XY, CALMA_I4, f);
    calmaOutI4Z(p.p_x, f);
    calmaOutI4Z(p.p_y, f);

    /* Text of label */
    calmaOutStringRecordZ(CALMA_STRING, lab->lab_text, f);

    /* End of element */
    calmaOutRHZ(4, CALMA_ENDEL, CALMA_NODATA, f);

    /* If the cifoutput layer is for labels only (has no operators),	*/
    /* and the label rectangle is not degenerate, then output the label	*/
    /* rectangle as a boundary with the label's layer:purpose pair.	*/

    if (type < 0)
	return;

    calmanum = CIFCurStyle->cs_layers[type]->cl_calmanum;
    if (!CalmaIsValidLayer(calmanum))
	return;

    calmatype = CIFCurStyle->cs_layers[type]->cl_calmatype;

    /* Note that the check for whether the CIF_LABEL_NOPORT flag has	*/
    /* been set is done outside of this routine.			*/

    if ((CIFCurStyle->cs_layers[type]->cl_ops == NULL) &&
		(lab->lab_rect.r_xtop > lab->lab_rect.r_xbot) &&
		(lab->lab_rect.r_ytop > lab->lab_rect.r_ybot))
    {
	Rect r;

	r = lab->lab_rect;
	r.r_xbot *= calmaWriteScale;
	r.r_ybot *= calmaWriteScale;
	r.r_xtop *= calmaWriteScale;
	r.r_ytop *= calmaWriteScale;

	/* Boundary */
	calmaOutRHZ(4, CALMA_BOUNDARY, CALMA_NODATA, f);

	/* Layer */
	calmaOutRHZ(6, CALMA_LAYER, CALMA_I2, f);
	calmaOutI2Z(calmanum, f);

	/* Data type */
	calmaOutRHZ(6, CALMA_DATATYPE, CALMA_I2, f);
	calmaOutI2Z(calmatype, f);

	/* Coordinates */
	calmaOutRHZ(44, CALMA_XY, CALMA_I4, f);
	calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ybot, f);
	calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ybot, f);
	calmaOutI4Z(r.r_xtop, f); calmaOutI4Z(r.r_ytop, f);
	calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ytop, f);
	calmaOutI4Z(r.r_xbot, f); calmaOutI4Z(r.r_ybot, f);

	/* End of element */
	calmaOutRHZ(4, CALMA_ENDEL, CALMA_NODATA, f);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaPaintLabelFuncZ --
 *
 * Filter function used to write out a single label corresponding to the
 * area of a paint tile, and having a text matching the CIF layer name.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the gzFile 'f'
 *
 * ----------------------------------------------------------------------------
 */

int
calmaPaintLabelFuncZ(
    Tile *tile,			/* Tile contains area for label. */
    TileType dinfo,		/* Split tile information (unused) */
    calmaOutputStructZ *cos)	/* File for output and clipping area */
{
    gzFile f = cos->f;
    const Rect *clipArea = cos->area;
    Rect r, r2;
    Point p;
    int len;
    CIFLayer *layer = CIFCurStyle->cs_layers[cos->type];

    if (IsSplit(tile)) return 0;    /* Ignore non-Manhattan geometry */

    if (!CalmaIsValidLayer(layer->cl_calmanum))
	return 0;

    TiToRect(tile, &r);

    if (clipArea != NULL)
	GeoClip(&r, clipArea);

    calmaOutRHZ(4, CALMA_TEXT, CALMA_NODATA, f);

    calmaOutRHZ(6, CALMA_LAYER, CALMA_I2, f);
    calmaOutI2Z(layer->cl_calmanum, f);

    calmaOutRHZ(6, CALMA_TEXTTYPE, CALMA_I2, f);
    calmaOutI2Z(layer->cl_calmatype, f);

    p.p_x = (r.r_xbot + r.r_xtop) * calmaPaintScale / 2;
    p.p_y = (r.r_ybot + r.r_ytop) * calmaPaintScale / 2;
    calmaOutRHZ(12, CALMA_XY, CALMA_I4, f);
    calmaOutI4Z(p.p_x, f);
    calmaOutI4Z(p.p_y, f);

    /* Text of label is the CIF layer name */
    calmaOutStringRecordZ(CALMA_STRING, layer->cl_name, f);

    /* End of element */
    calmaOutRHZ(4, CALMA_ENDEL, CALMA_NODATA, f);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaOutHeader --
 *
 * Output the header description for a Calma file.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the gzFile 'f'.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaOutHeaderZ(
    CellDef *rootDef,
    gzFile f)
{
    static double useru = 0.001;
    static double mum = 1.0e-9;

    /* GDS II version 3.0 */
    calmaOutRHZ(6, CALMA_HEADER, CALMA_I2, f);
    calmaOutI2Z(3, f);

    /* Beginning of library */
    calmaOutRHZ(28, CALMA_BGNLIB, CALMA_I2, f);
    if (CalmaDateStamp != NULL)
    	calmaOutDateZ(*CalmaDateStamp, f);
    else
    	calmaOutDateZ(rootDef->cd_timestamp, f);
    calmaOutDateZ(time((time_t *) 0), f);

    /* Library name (name of root cell) */
    calmaOutStructNameZ(CALMA_LIBNAME, rootDef, f);

    /*
     * Units.
     * User units are microns; this is really unimportant.
     *
     * Database units are nanometers, since there are
     * programs that don't understand anything else.  If
     * the database units are *smaller* than nanometers, use
     * the actual database units.  Otherwise, stick with
     * nanometers, because anything larger may not input
     * properly with other software.
     */

    calmaOutRHZ(20, CALMA_UNITS, CALMA_R8, f);
    if (CIFCurStyle->cs_flags & CWF_ANGSTROMS) useru = 0.0001;
    calmaOutR8Z(useru, f);	/* User units per database unit */

    if (CIFCurStyle->cs_flags & CWF_ANGSTROMS) mum = 1e-10;
    calmaOutR8Z(mum, f);	/* Meters per database unit */
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaOutDateZ --
 *
 * Output a date/time specification to the FILE 'f'.
 * This consists of outputting 6 2-byte quantities,
 * or a total of 12 bytes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the gzFile 'f'.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaOutDateZ(
    time_t t,	/* Time (UNIX format) to be output */
    gzFile f)	/* Stream file */
{
    struct tm *datep = localtime(&t);

    calmaOutI2Z(datep->tm_year, f);
    calmaOutI2Z(datep->tm_mon+1, f);
    calmaOutI2Z(datep->tm_mday, f);
    calmaOutI2Z(datep->tm_hour, f);
    calmaOutI2Z(datep->tm_min, f);
    calmaOutI2Z(datep->tm_sec, f);
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaOutStringRecordZ --
 *
 * Output a complete string-type record.  The actual record
 * type is given by 'type'.  Up to the first CALMANAMELENGTH characters
 * of the string 'str' are output.  Any characters in 'str'
 * not in the legal Calma stream character set are output as
 * 'X' instead.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the gzFile 'f'.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaOutStringRecordZ(
    int type,	/* Type of this record (data type is ASCII string) */
    char *str,	/* String to be output */
    gzFile f)	/* Compressed stream file */
{
    int len;
    unsigned char c;
    const char *table;
    char *locstr, *origstr = NULL;
    char *locstrprv;

    if(CIFCurStyle->cs_flags & CWF_PERMISSIVE_LABELS)
    {
	table = calmaMapTablePermissive;
    } else {
	table = calmaMapTableStrict;
    }

    len = strlen(str);
    locstr = str;

    /*
     * Make sure length is even.
     * Output at most CALMANAMELENGTH characters.
     * If the name is longer than CALMANAMELENGTH, then output the
     * last CALMANAMELENGTH characters (since cell names are more
     * likely to be unique in the last characters than in the first
     * characters).
     *
     * NOTE:  GDS format has not used CALMANAMELENGTH restrictions
     * for ages.  Since this is a 2-byte record, then is it not
     * worth checking the 65536 - 4 character limit.  The CALMANAMELENGTH
     * restriction must be enabled in the cifoutput flags.
     */


    if (len & 01) len++;
    if ((CIFCurStyle->cs_flags & CWF_STRING_LIMIT) && (len > CALMANAMELENGTH))
    {
	TxError("Warning:  Cellname %s truncated ", str);
	TxError("to %s (GDS format limit)\n", str + len - CALMANAMELENGTH);
	locstr = str + len - CALMANAMELENGTH;
	len = CALMANAMELENGTH;
    }
    calmaOutI2Z(len+4, f);	/* Record length */
    (void) gzputc(f, type);		/* Record type */
    (void) gzputc(f, CALMA_ASCII);	/* Data type */

    /* Output the string itself */
    while (len--)
    {
	locstrprv = locstr;
	c = (unsigned char) *locstr++;
	if (c == 0) gzputc(f, '\0');
	else
	{
	    if ((c > 127) || (c == 0))
	    {
		TxError("Warning: Unprintable character changed "
			"to \'X\' in label.\n");
		c = 'X';
	    }
	    else
	    {
		if (((unsigned char)table[c] != c) && (origstr == NULL))
		    origstr = StrDup(NULL, str);

		c = table[c];
		locstrprv[0] = c;
	    }
	    if (!CalmaDoLower && islower(c))
		(void) gzputc(f, toupper(c));
	    else
		(void) gzputc(f, c);
	}
    }
    if (origstr != NULL)
    {
	TxError("Warning: characters changed in string \'%s\'; "
		"modified string is \'%s\'\n", origstr, str);
	freeMagic(origstr);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * calmaOutR8Z --
 *
 * Write an 8-byte Real value in GDS-II format to the output stream
 * The value is passed as a double.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	8-byte value written to compressed output stream gzFile 'f'.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaOutR8Z(
    double d,	/* Double value to write to output */
    gzFile f)	/* Stream file */
{
    int c, i, sign, expon;

    /* mantissa must be 64 bits for this routine to work correctly */
    uint64_t mantissa;

    mantissa = 0;
    if (d == 0.0)
    {
	sign = 0;
	expon = 0;
    }
    else
    {
	if (d > 0.0)
	    sign = 0;
	else
	{
	    sign = 1;
	    d = -d;
	}

	expon = 64;
	while (d >= 1.0)
	{
	    d /= 16.0;
	    expon++;
	}
	while (d < 0.0625)
	{
	    d *= 16.0;
	    expon--;
	}

	for (i = 0; i < 64; i++)
	{
	    mantissa <<= 1;
	    if (d >= 0.5)
	    {
		mantissa |= 0x1;
		d -= 0.5;
	    }
	    d *= 2.0;
	}
    }
    c = (sign << 7) | expon;
    (void) gzputc(f, c);
    for (i = 1; i < 8; i++)
    {
	c = (int)(0xff & (mantissa >> (64 - (8 * i))));
	(void) gzputc(f, c);
    }
}


/*
 * ----------------------------------------------------------------------------
 *
 * calmaOut8Z --
 *
 * Output 8 bytes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the gzFile 'f'.
 *
 * ----------------------------------------------------------------------------
 */

void
calmaOut8Z(
    const char *str,	/* 8-byte string to be output */
    gzFile f)	/* Compressed stream file */
{
    int i;

    for (i = 0; i < 8; i++)
	(void) gzputc(f, *str++);
}

#endif /* HAVE_ZLIB */
