static char *SccsId = "@(#)cga.c 4.10 (TU-Delft) 01/16/92";
/**********************************************************

Name/Version      : cga/4.10

Language          : C
Operating system  : UNIX SYSTEM V
Host machine      : HP9000/500

Author(s)         : R. Paulussen
Creation date     : 15-Dec-1987
Modified by       : S. de Graaf
Modification date : 18-Mar-1988
Modification date : 17-Dec-1990 (4.04)
Modification date : 25-Feb-1991 (4.07)
Modification date : 12-Dec-1991 (4.08)
Modification date : 13-Dec-1991 (4.09)
Modification date : 16-Jan-1992 (4.10)


        Delft University of Technology
        Department of Electrical Engineering
        Network Theory Section
        Mekelweg 4 - P.O.Box 5031
        2600 GA DELFT
        The Netherlands

        Phone : 015 - 786234

        COPYRIGHT (C) 1988, All rights reserved
***********************************************************
MADE BY:
	Delft University of Technology
	Department of Submicron Technology
	Lorentzweg 1
	DELFT
	The Netherlands
***********************************************************
PROCESS DESCRIPTION:
	Cga converts a GDS formatted file into ASCII.
	The ASCII-output is sent to the standard output.
**********************************************************/
#include "stdio.h"

#define PE fprintf (stderr,
#define PO fprintf (stdout,

#define BLOCKSIZE    2048
#define NR_REC_TYPES 57

#ifdef SYSV
#define index strchr
#endif

/* record types: */
#define BGNLIB  1
#define UNITS   3
#define ENDLIB  4
#define BGNSTR  5
#define XY     16

static
char   *rec_dscr[NR_REC_TYPES] =
{
    "HEADER", "BGNLIB", "LIBNAME", "UNITS", "ENDLIB",
    "BGNSTR", "STRNAME", "ENDSTR", "BOUNDARY", "PATH",
    "SREF", "AREF", "TEXT", "LAYER", "DATATYPE",
    "WIDTH", "XY", "ENDEL", "SNAME", "COLROW",
    "TEXTNODE", "NODE", "TEXTTYPE", "PRESENTATION", "SPACING",
    "STRING", "STRANS", "MAG", "ANGLE", "UINTEGER",
    "USTRING", "REFLIBS", "FONTS", "PATHTYPE", "GENERATIONS",
    "ATTRTABLE", "STYPTABLE", "STRTYPE", "ELFLAGS", "ELKEY",
    "LINKTYPE", "LINKKEYS", "NODETYPE", "PROPATTR", "PROPVALUE",
    "BOX", "BOXTYPE", "PLEX", "BGNEXTN", "ENDEXTN",
    "TAPENUM", "TAPECODE", "STRCLASS", "RESERVED", "FORMAT",
    "MASK", "ENDMASKS"
};

static
char   *rec_short[NR_REC_TYPES] =
{
    "HE", "BL", "LI", "UN", "EL", "BS", "NA", "ES", "BD", "PA",
    "SR", "AR", "TE", "LA", "DA", "WI", "XY", "EE", "SN", "CO",
    "TN", "NO", "TT", "PS", "SP", "SI", "ST", "MG", "AN", "UI",
    "US", "RL", "FO", "PT", "GE", "AT", "SB", "SY", "EF", "EK",
    "LT", "LK", "NT", "PR", "PV", "BO", "BT", "PX", "BX", "EX",
    "TA", "TC", "SC", "RE", "FM", "MA", "EM"
};

unsigned
char    buffer[BLOCKSIZE];	/* buffer of 1 tape block */
char    buf[80];

int     stdout_closed = 0;
int     record_len;
int     file_len;
int     data_type;
int     record_type = -1;
int     rec_nr = 1;
int     nflag;
int     oflag = -1;
int     sflag;
int     tflag;
int     uflag;
int     xflag;
int     usage;
int     uuf = 1;

FILE   *fp_gds;

char   *argv0 = "cga";		/* Program Name */
char   *use_msg =		/* Command Line */
"\nUsage: %s [-n] [-oRT[,C]] [-pRN[,R2]] [-s] [-t] [-u[F]] [-x] GDS-file\n\n";

main (argc, argv)
int   argc;
char *argv[];
{
    char *index ();
    char *cp;
    int rec_len;
    int file_size;
    int last_record_type = -1;
    int i;
    int j = sizeof (int);
    int o_rec, o_cnt;
    int rec1 = 1;
    int rec2 = 0X7FFFFFFE;
    register int skipflag = 0;
    register unsigned char *bp;

    for (i = 1; i < argc && *argv[i] == '-'; ++i) {
	switch (argv[i][1]) {
	case 'n':
	    nflag = 1;
	    break;
	case 'o':
	    o_cnt = 0;
	    if (cp = index (argv[i], ',')) {
		*cp++ = '\0';
		if ((o_cnt = atoi (cp)) < 0) o_cnt = 0;
	    }
	    o_rec = o_cnt;
	    oflag = atoi (&argv[i][2]);
	    if (oflag >= NR_REC_TYPES || oflag < 0) {
		++usage;
		PE "%s: -o: illegal record type: %d\n", argv0, oflag);
	    }
	    break;
	case 'p':
	    if (cp = index (argv[i], ',')) *cp++ = '\0';
	    if ((rec1 = atoi (&argv[i][2])) < 1) rec1 = 1;
	    rec2 = rec1;
	    if (cp && (rec2 = atoi (cp)) < rec1) rec2 = rec1;
	    break;
	case 's':
	    sflag = 1;
	    break;
	case 't':
	    tflag = 1;
	    break;
	case 'x':
	    xflag = 1;
	    break;
	case 'u':
	    if (argv[i][2]) {
		if ((uuf = atoi (&argv[i][2])) <= 0) {
		    ++usage;
		    PE "%s: -u: illegal mul. factor: %d\n", argv0, uuf);
		}
	    }
	    else uflag = 1;
	    break;
	default:
	    ++usage;
	    PE "%s: %s: unknown option\n", argv0, argv[i]);
	    break;
	}
    }

    if (tflag) {
	PO "%s: RT:  SN:  LNAME:\n", argv0);
	PO "%s: ----------------\n", argv0);
	for (i = 0; i < NR_REC_TYPES; ++i)
	    PO "%s: %2d - %s - %s\n", argv0, i, rec_short[i], rec_dscr[i]);
	goto fini;
    }

    if (i >= argc) {
	++usage;
	PE "%s: no GDS file given\n", argv0);
    }
    else if (i < argc - 1) {
	++usage;
	PE "%s: too many arguments given\n", argv0);
    }
    if (usage) {
	PE use_msg, argv0);
	exit (1);
    }

    if (j != 4) {
	PE "%s: error: sizeof int not equal to 4 bytes\n", argv0);
	exit (1);
    }

    if (!(fp_gds = fopen (argv[i], "r"))) {
	PE "%s: cannot open GDS file '%s'\n", argv0, argv[i]);
	exit (1);
    }

    fseek (fp_gds, 0L, 2);
    file_size = file_len = ftell (fp_gds);
    if (file_len & 1) pr_exit (1, file_len);
    fseek (fp_gds, 0L, 0);

    if (uflag) read_units ();

    /*
    ** Process GDS input data:
    */
    while (file_len >= 4) {
	if (!fread ((char *) buffer, 4, 1, fp_gds)) pr_exit (2, 0);
	file_len -= 4;
	bp = buffer;
	record_len  = *bp++;
	record_len  = (record_len << 8) | *bp++;
	last_record_type = record_type;
	record_type = *bp++;
	data_type   = *bp;

	if (record_type >= NR_REC_TYPES || record_type < 0)
	    pr_exit (8, record_type);

	if (rec_nr < rec1) ++skipflag;
	else if (oflag >= 0) {
	    if (record_type == oflag) o_rec = 0;
	    else if (++o_rec > o_cnt) ++skipflag;
	}

	if (record_len < 4) {
	    if (record_len == 0 && record_type == 0 && data_type == 0) {
		if (!skipflag) {
		    if (nflag) PO "%d:", rec_nr);
		    PO "%s %d\n", sflag ? "PD" : "PADDING", file_len + 4);
		}
		break; /* padding found */
	    }
	    pr_exit (3, record_len);
	}
	if (record_len & 1) pr_exit (4, record_len);

	if (!skipflag) {
	    if (nflag) PO "%d:", rec_nr);
	    PO "%s", sflag ? rec_short[record_type] : rec_dscr[record_type]);
	}

	if ((record_len -= 4) == 0) {
	    if (data_type == 0) goto next;
	    pr_exit (7, data_type);
	}

	rec_len = record_len;
	do {
	    if ((record_len = rec_len) > BLOCKSIZE) record_len = BLOCKSIZE;
	    if (!fread ((char *) buffer, record_len, 1, fp_gds)) pr_exit (5, 0);
	    file_len -= record_len;

	    switch (data_type) {
	    case 0:
		pr_exit (7, data_type);
		break;
	    case 1:
		if (record_len % 2) pr_exit (7, data_type);
		if (!skipflag) read_bit ();
		break;
	    case 2:
		if (record_type == BGNLIB || record_type == BGNSTR) {
		    if (record_len != 24) pr_exit (7, data_type);
		}
		else {
		    if (record_len % 2) pr_exit (7, data_type);
		}
		if (!skipflag) read_integer (2);
		break;
	    case 3:
		if (record_type == XY) {
		    if (record_len % 8) pr_exit (7, data_type);
		}
		else {
		    if (record_len % 4) pr_exit (7, data_type);
		}
		if (!skipflag) read_integer (4);
		break;
	    case 4:
		pr_exit (7, data_type);
		break;
	    case 5:
		if (record_len % 8) pr_exit (7, data_type);
		if (!skipflag) read_double ();
		break;
	    case 6:
		if (!skipflag) read_string ();
		break;
	    default:
		pr_exit (6, data_type);
	    }
	} while ((rec_len -= record_len) > 0);
next:
	if (!skipflag) PO "\n");
	else skipflag = 0;
	if (++rec_nr > rec2) break;
    }
    fclose (stdout);
    stdout_closed = 1;

    if (rec_nr > rec2) goto fini;

    if (last_record_type != ENDLIB) {
	PE "%s: warning: last record is not ENDLIB\n", argv0);
    }
    if (file_len == 0) {
	if (file_size % BLOCKSIZE)
	    PE "%s: warning: no padding found\n", argv0);
	goto fini;
    }
    if (file_len >= BLOCKSIZE - 4) {
	PE "%s: warning: too many padding found\n", argv0);
	goto fini; /* note: read buffer too small */
    }
    if (!fread ((char *) buffer, file_len, 1, fp_gds)) {
	PE "%s: read error while reading padding\n", argv0);
	goto fini;
    }
    bp = buffer;
    while (file_len--) {
	if (*bp++) {
	    PE "%s: warning: padding not null\n", argv0);
	    break;
	}
    }
fini:
    PE "%s: -- program finished --\n", argv0);
    exit (0);
}

read_units ()
{
    double  ln, ldexp ();
    register unsigned char *bp;
    register int e_h;
    unsigned int m_l, m_h;
    int fl = file_len;

    while (fl >= 4) {
	if (!fread ((char *) buffer, 4, 1, fp_gds)) pr_exit (2, 0);
	fl -= 4;
	bp = buffer;
	record_len  = *bp++;
	record_len  = (record_len << 8) | *bp++;
	record_type = *bp++;
	data_type   = *bp;

	if (record_len < 4) {
	    if (record_len == 0 && record_type == 0 && data_type == 0) {
		break; /* padding found */
	    }
	    pr_exit (3, record_len);
	}
	if ((record_len -= 4) == 0) goto next;

	if (record_len > BLOCKSIZE) pr_exit (0, record_len + 4);
	if (!fread ((char *) buffer, record_len, 1, fp_gds)) pr_exit (5, 0);
	fl -= record_len;

	if (data_type == 5) {
	    if (record_len % 8) pr_exit (7, data_type);
	    if (record_type == UNITS) {
		uflag = 0;
		bp = buffer;
		e_h = *bp++;
		m_h = *bp++;
		m_h = (m_h << 8) | *bp++;
		m_h = (m_h << 8) | *bp++;
		m_l = *bp++;
		m_l = (m_l << 8) | *bp++;
		m_l = (m_l << 8) | *bp++;
		m_l = (m_l << 8) | *bp++;
		if (m_h) {
		    ln = m_l;
		    ln = ldexp (ln + 4294967296.0 * m_h, -56);
		    ln = ldexp (ln, 4 * ((e_h & 0X7F) - 64));
		    if (e_h & 0X80) ln = -ln;
		}
		else ln = 0;
		uuf = 1.0 / ln + 0.01;
		ln = 1.0 - ln * uuf;
		if (ln < -0.001 || ln > 0.001) {
		    uuf = 1;
		    PE "%s: option -u: sorry, cannot been used!\n", argv0);
		}
		break;
	    }
	}
next:
	if (++rec_nr > 10) break;
    }
    if (uflag) {
	PE "%s: option -u: cannot find UNITS record\n", argv0);
    }
    fseek (fp_gds, 0L, 0); /* rewind */
    rec_nr = 1;
}

read_integer (type) /* Read 2 or 4 byte integer numbers */
int type;
{
    register int i, nr, value;
    register char *s, *c;
    register unsigned char *bp = buffer;

    nr = (type == 4) ? record_len >> 2 : record_len >> 1;

    s = buf; *s++ = ' ';
    for (i = 0; i < nr; ++i) {
	if (xflag && record_type == XY) *buf = (i % 2) ? ' ' : '\n';
	value = *bp++;
	value = (value << 8) | *bp++;
	if (type == 4) {
	    value = (value << 8) | *bp++;
	    value = (value << 8) | *bp++;
	    if (uuf != 1) {
		if (!(value % uuf)) {
		    sprintf (s, "%d", value / uuf);
		}
		else {
		    sprintf (s, "%.3f", (double)value / uuf);
		    while (*++s != '.');
		    c = s + 3;
		    if (*c == '0') if (*--c == '0') --c;
		    *++c = 0;
		    s = buf + 1;
		}
	    }
	    else sprintf (s, "%d", value);
	}
	else sprintf (s, "%d", value);
	PO buf);
    }
}

read_string () /* Read ASCII string */
{
    buffer[record_len] = '\0';
    PO " %s", buffer);
}

read_bit () /* Read bit-array words */
{
    register int i, nr, mask, word;
    register char *cp;
    register unsigned char *bp = buffer;

    nr = record_len >> 1;
    for (i = 0; i < nr; ++i) {
	cp = buf;
	*cp++ = ' ';
	word = *bp++;
	word = (word << 8) | *bp++;
	for (mask = 0X8000; mask; mask >>= 1) { /* for every bit */
	    *cp++ = (mask & word) ? '1' : '0';
	}
	*cp = '\0';
	PO "%s", buf);
    }
}

read_double () /* Read 8 byte real numbers */
{
    double  ln, ldexp ();
    register unsigned char *bp = buffer;
    register int i, nr, e_h;
    unsigned int m_l, m_h;

    nr = record_len >> 3;

    for (i = 0; i < nr; ++i) {
	e_h = *bp++;
	m_h = *bp++;
	m_h = (m_h << 8) | *bp++;
	m_h = (m_h << 8) | *bp++;
	m_l = *bp++;
	m_l = (m_l << 8) | *bp++;
	m_l = (m_l << 8) | *bp++;
	m_l = (m_l << 8) | *bp++;
	if (m_h) {
	    ln = m_l;
	    ln = ldexp (ln + 4294967296.0 * m_h, -56);
	    ln = ldexp (ln, 4 * ((e_h & 0X7F) - 64));
	    if (e_h & 0X80) ln = -ln;
	}
	else ln = 0;
	if (record_type == UNITS) {
	    PO " %-7.4G", ln * uuf);
	}
	else PO " %-7.4G", ln);
    }
}

pr_exit (errno, nr)
int errno, nr;
{
    if (!stdout_closed) {
	PO "\n");
	fclose (stdout);
    }
    PE "%s: %d: error: ", argv0, rec_nr);
    switch (errno) {
	case 0: PE "too long record length (= %d)", nr); break;
	case 1: PE "odd file length (= %d)", nr); break;
	case 2: PE "cannot read record header"); break;
	case 3: PE "too small record length (= %d)", nr); break;
	case 4: PE "odd record length (= %d)", nr); break;
	case 5: PE "cannot read record"); break;
	case 6: PE "unknown data type (= %d)", nr); break;
	case 7: PE "data type %d, incorrect # of bytes (= %d)",
		    nr, record_len); break;
	case 8: PE "unknown record type (= %d)", nr); break;
    }
    PE "\n%s: -- program aborted --\n", argv0);
    exit (1);
}
