/*
 * plotPS.c --
 *
 * This file contains procedures that generate PS-format files
 * to describe a section of layout.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/plot/plotPS.c,v 1.3 2010/06/24 12:37:25 tim Exp $";
#endif  /* not lint */

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

#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "utils/tech.h"
#include "utils/malloc.h"
#include "utils/utils.h"
#include "windows/windows.h"
#include "commands/commands.h"
#include "dbwind/dbwind.h"
#include "textio/textio.h"

/* Records of the following type are used to describe how to generate
 * PS output for a particular set of mask layers.  Each style
 * describes the PS figures to draw for a particular set of
 * layers.  A single layer may participate in several ps styles.
 */

typedef struct psstyle
{
    TileTypeBitMask grs_layers;		/* Layers to plot in this style. */
    int grs_stipple;			/* Index of fill to use. */
    int grs_color;			/* Index of color to use. */
    struct psstyle *grs_next;	/* Next style in chain. */
} PSStyle;

typedef struct pspattern
{
    int 		index;
    unsigned long	stipple[8];
    struct pspattern	*pat_next;
} PSPattern;

typedef struct pscolor
{
    int			index;
    unsigned char	color[4];
    struct pscolor	*col_next;
} PSColor;


static PSStyle *plotPSStyles = NULL;
static PSPattern *plotPSPatterns = NULL;
static PSColor	*plotPSColors = NULL;

int delta, xnmargin, ynmargin, xpmargin, ypmargin;
float fscale;

/* Most of the grs_stipple values are PS stipple numbers.  However,
 * if a grs_stipple value is less than zero, it means something special.
 * The definitions below give the possible alternatives:
 *
 * CROSS:		Draw a thick outline around the tile with
 *			a cross through it (used for contacts).
 * BORDER:		Same as CROSS, except draw the outline with
 *			no cross through it.
 * SOLID:		This is the same as a solid stipple but renders
 *			much faster.
 */

#define CROSS  -1
#define BORDER -2
#define SOLID  -3

/* The definitions below give the integers used for various PS
 * line drawing styles (brushes).
 */

#define PS_THIN		1
#define PS_MEDIUM	2
#define PS_THICK	3

/* The variables below are used to pass information from the top-level
 * procedure PlotPS down to the lower-level search functions
 * that are invoked for pieces of the layout.
 */

static FILE *file;		/* File to use for output. */
static PSStyle *curStyle;	/* Current style being output. */
static PSColor *curColor;	/* Current color being output. */
static PSPattern *curPattern;	/* Current pattern being output. */
static int curLineWidth;	/* Current line width */
static int curFont;		/* Current font */
static TileTypeBitMask curMask;	/* Layers currently being searched:  this
				 * is the AND of the mask from curStyle and
				 * the layers that the user specified.
				 */
static Rect bbox;		/* Bounding box, in root coordinates, of
				 * area being plotted.
				 */

/* Parameters passed to the plotting process */

static char *defaultBoldFont = "/HelveticaBold";
static char *defaultFont = "/Helvetica";
char *PlotPSIdFont = NULL;
char *PlotPSNameFont = NULL;
char *PlotPSLabelFont = NULL;
int PlotPSIdSize = 8;
int PlotPSNameSize = 12;
int PlotPSLabelSize = 12;
int PlotPSBoundary = 1;	 /* Print boundaries around all layers */
int PlotPSHeight = 792;  /* 11 inches * 72 PS units/inch */
int PlotPSWidth = 612;   /* 8.5 inches */
int PlotPSMargin = 72;   /* 1 inch */

int curx1, curx2, cury1, cury2;		    /* Last encountered line */
int curxbot, curybot, curwidth, curheight;  /* Last encountered rectangle */

/*
 * ----------------------------------------------------------------------------
 *
 * PSReset()
 *
 *	Plot optimization:   Reset buffered line and rectangle to default values
 *
 * ----------------------------------------------------------------------------
 */

void
PSReset()
{
    curxbot = curybot = curwidth = curheight = -2;
    curx1 = curx2 = cury1 = cury2 = -2;
}


/*
 * ----------------------------------------------------------------------------
 *	PlotPSTechInit --
 *
 * 	Called once at beginning of technology file read-in to initialize
 *	data structures.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Clears out the list of things to plot.
 * ----------------------------------------------------------------------------
 */

void
PlotPSTechInit()
{
    int i, j;
    PSStyle *style;
    PSColor *color;
    PSPattern *pattern;

    /* Clear out any old information */

    for (style = plotPSStyles; style != NULL; style = style->grs_next)
    {
	freeMagic((char *) style);
    }
    plotPSStyles = NULL;

    for (pattern = plotPSPatterns; pattern != NULL; pattern = pattern->pat_next)
    {
	freeMagic((char *) pattern);
    }
    plotPSPatterns = NULL;

    for (color = plotPSColors; color != NULL; color = color->col_next)
    {
	freeMagic((char *) color);
    }
    plotPSColors = NULL;

    if (!PlotPSIdFont)
	StrDup(&PlotPSIdFont, defaultFont);
    if (!PlotPSNameFont)
	StrDup(&PlotPSNameFont, defaultBoldFont);
    if (!PlotPSLabelFont)
	StrDup(&PlotPSLabelFont, defaultFont);
}

/*
 * ----------------------------------------------------------------------------
 *	PlotPSTechLine --
 *
 * 	This procedure is invoked by the technology module once for
 *	each line in the "ps" subsection of the "plot" section
 *	of the technology file.
 *
 * Results:
 *	Always returns TRUE (otherwise the technology module would
 *	abort Magic with a fatal error).
 *
 * Side effects:
 *	Builds up the table of PS styles.
 * ----------------------------------------------------------------------------
 */

bool
PlotPSTechLine(sectionName, argc, argv)
    char *sectionName;		/* Name of this section (unused). */
    int argc;			/* Number of arguments on line. */
    char *argv[];		/* Pointers to fields of line. */
{
    PSStyle *newstyle;
    PSColor *newcolor;
    PSPattern *newpattern;
    int i, color, stipple;

    if (argc != 9 && argc != 5 && argc != 3)
    {
	TechError("\"ps\" lines must have either 9, 5, or 3 arguments.\n");
	return TRUE;
    }

    if (argc == 9)	/* pattern definition */
    {
	newpattern = (PSPattern *) mallocMagic(sizeof(PSPattern));
	sscanf(argv[0], "%d", &(newpattern->index));
	for(i = 0; i < 8; i++)
	{
	    sscanf(argv[1 + i], "%08lx", &(newpattern->stipple[i]));
	}
	newpattern->pat_next = plotPSPatterns;
	plotPSPatterns = newpattern;
    }
    else if (argc == 5)	/* color definition */
    {
	int tmpint;
	newcolor = (PSColor *) mallocMagic(sizeof(PSColor));
	sscanf(argv[0], "%d", &(newcolor->index));
	for(i = 0; i < 4; i++)
	{
	    sscanf(argv[1 + i], "%d", &tmpint);
	    newcolor->color[i] = (unsigned char)(tmpint & 0xff);
	}
	newcolor->col_next = plotPSColors;
	plotPSColors = newcolor;
    }
    else {   /* 3 args: layer definition */
	if (!StrIsInt(argv[1]))
	{
	    TechError("2nd field must be an integer\n");
	    return TRUE;
	}
	color = atoi(argv[1]);

	if (strcmp(argv[2], "X") == 0)
	    stipple = CROSS;
	else if (strcmp(argv[2], "B") == 0)
	    stipple = BORDER;
	else if (strcmp(argv[2], "S") == 0)
	    stipple = SOLID;
	else
	{
	    if (!StrIsInt(argv[2]))
	    {
		TechError("3rd field must be an integer or \"S\", \"X\", or \"B\".\n");
		return TRUE;
	    }
	    stipple = atoi(argv[2]);
        }

	newstyle = (PSStyle *) mallocMagic(sizeof(PSStyle));

	DBTechNoisyNameMask(argv[0], &newstyle->grs_layers);

	/* Replace non-primary contact images with primary images. */

	for (i = TT_TECHDEPBASE; i < DBNumTypes; i++)
	{
	    if TTMaskHasType(&newstyle->grs_layers, i)
		TTMaskSetMask(&newstyle->grs_layers, &DBLayerTypeMaskTbl[i]);
	}
	TTMaskAndMask(&newstyle->grs_layers, &DBUserLayerBits);
	newstyle->grs_stipple = stipple;
	newstyle->grs_color = color;
	newstyle->grs_next = plotPSStyles;
	plotPSStyles = newstyle;
    }

    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * plotPSFlushRect()
 *
 *	Plot optimization:   Draw last buffered rectangle.
 *
 * ----------------------------------------------------------------------------
 */

void
plotPSFlushRect(style)
    int style;
{
    if (curwidth > 0)
    {
	if (style == SOLID)
	    fprintf(file, "%d %d %d %d ms\n", curxbot, curybot,
			curwidth, curheight);
	else
	    fprintf(file, "%d %d %d %d fb\n", curxbot, curybot,
			curxbot + curwidth, curybot + curheight);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * plotPSFlushLine()
 *
 *	Plot optimization:   Draw last buffered line.
 *
 * ----------------------------------------------------------------------------
 */

void
plotPSFlushLine()
{
    if (cury1 == cury2)
    {
	if (curx1 != curx2)	/* true only if nothing is buffered */
	    fprintf(file, "%d %d %d hl\n", curx2 - curx1, curx1, cury1);
    }
    else if (curx1 == curx2)
	fprintf(file, "%d %d %d vl\n", cury2 - cury1, curx1, cury1);
    else
	fprintf(file, "%d %d %d %d ml\n", curx1, cury1, curx2, cury2);
}


/*
 * ----------------------------------------------------------------------------
 *
 * plotPSLine --
 *
 * 	Outputs a line into the current PS file.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	I/O.
 *
 * ----------------------------------------------------------------------------
 */

void
plotPSLine(p1, p2)
    Point *p1, *p2;		/* Endpoints of line, given in root
				 * coordinates.
				 */
{
    int x1, x2, y1, y2, limit, diff;
    bool tmptf;

    /* Clip the line to the rectangular area being output.  First,
     * arrange for the first x-coordinate to be the smaller, then
     * clip against vertical lines at the x-boundaries.
     */

    if (p1->p_x <= p2->p_x)
    {
	x1 = p1->p_x - bbox.r_xbot;
	x2 = p2->p_x - bbox.r_xbot;
	y1 = p1->p_y - bbox.r_ybot;
	y2 = p2->p_y - bbox.r_ybot;
    }
    else
    {
	x1 = p2->p_x - bbox.r_xbot;
	x2 = p1->p_x - bbox.r_xbot;
	y1 = p2->p_y - bbox.r_ybot;
	y2 = p1->p_y - bbox.r_ybot;
    }
    limit = bbox.r_xtop - bbox.r_xbot;
    if ((x1 > limit) || (x2 < 0)) return;

    /* Now clip against horizontal lines at the y-boundaries. */

    if (y2 < y1)
    {
	float tmp;
	tmp = y2; y2 = y1; y1 = tmp;
	tmp = x2; x2 = x1; x1 = tmp;
    }
    limit = bbox.r_ytop - bbox.r_ybot;
    if ((y1 > limit) || (y2 < 0)) return;

    /* compare against last output line and merge if possible */
    if (((x1 == x2) && (x1 == curx1) && (x2 == curx2)
		&& ((tmptf = (y1 == cury2)) || (y2 == cury1))))
    {
	if (tmptf) cury2 = y2;
	else cury1 = y1;
    }
    else if (((y1 == y2) && (y1 == cury1) && (y2 == cury2)
		&& ((tmptf = (x1 == curx2)) || (x2 == curx1))))
    {
	if (tmptf) curx2 = x2;
	else curx1 = x1;
    }
    else
    {
	plotPSFlushLine();
	curx1 = x1;
	curx2 = x2;
	cury1 = y1;
	cury2 = y2;
    }
}


/*
 * ----------------------------------------------------------------------------
 *
 * plotPSRect --
 *
 * 	Outputs PS statements to draw a rectangular area as
 *	an outline with a given line style.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Adds information to the current PS file.
 *
 * ----------------------------------------------------------------------------
 */

void
plotPSRect(rect, style)
    Rect *rect;	/* Rectangle to be drawn, in root coords. */
    int style;
{
    int x, y, w, h;

    /* Output all boxes with any part visible.  Depend on PostScript to */
    /* do the clipping of any boxes crossing the plot boundary.		*/

    x = rect->r_xbot - bbox.r_xbot;
    if ((x < 0) || (rect->r_xbot > bbox.r_xtop)) return;
    w = rect->r_xtop - rect->r_xbot;
    y = rect->r_ybot - bbox.r_ybot;
    if ((y < 0) || (rect->r_ybot > bbox.r_ytop)) return;
    h = rect->r_ytop - rect->r_ybot;

    fprintf(file, "%d %d %d %d m%c\n", x, y, w, h, (style == CROSS) ? 'x' :
		(style == SOLID) ? 's' : 'r');
}


/*
 * ----------------------------------------------------------------------------
 *
 * plotPSPaint --
 *
 * 	This procedure is invoked once for each paint rectangle in
 *	the area being plotted.
 *
 * Results:
 *	Always returns 0 to keep the search alive.
 *
 * Side effects:
 *	Outputs information for the tile, including stipple for its
 *	interior, and a solid line for any portion of the boundary
 *	of the tile that is adjacent to a tile NOT in this style.
 *
 * ----------------------------------------------------------------------------
 */

int
plotPSPaint(tile, cxp)
    Tile *tile;			/* Tile that's of type to be output. */
    TreeContext *cxp;		/* Describes search in progress. */
{
    Rect tileArea, edge, rootArea;
    int xbot, width, ybot, height;
    Tile *neighbor;
    SearchContext *scx = cxp->tc_scx;
    bool tmptf;
    TileType ntype;

    /* First transform tile coords to root coords */

    TiToRect(tile, &tileArea);
    GeoTransRect(&scx->scx_trans, &tileArea, &rootArea);

    /* See if this tile gets special handling. */

    if ((curStyle->grs_stipple == CROSS) || (curStyle->grs_stipple == BORDER))
    {
	/* Draw tile as a thick outline with a cross from corner
	 * to corner, and skip the rest of this procedure.
	 */

	Point ul, lr;

	if (curLineWidth != PS_MEDIUM) {
	    fprintf(file, "l2\n");
	    curLineWidth = PS_MEDIUM;
	}

	plotPSRect(&rootArea, curStyle->grs_stipple);
	return 0;
    }

    /* If this is a triangle, output the last rect and deal with this one */
    /* individually.							  */

    if (IsSplit(tile))
    {
	int np, i, j;
	TileType dinfo;
	Point polyp[5];

	plotPSFlushRect(curStyle->grs_stipple);
	plotPSFlushLine();
	PSReset();

	/* Side and direction are altered by geometric transformations */

	dinfo = DBTransformDiagonal(TiGetTypeExact(tile), &scx->scx_trans);

	/* Use GrClipTriangle() routine to get the n-sided polygon that */
	/* results from clipping a triangle to the clip region.		*/

	GrClipTriangle(&rootArea, &bbox, TRUE, dinfo, polyp, &np);
	for (i = 0; i < np; i++)
	{
	   polyp[i].p_x -= bbox.r_xbot;
	   polyp[i].p_y -= bbox.r_ybot;
	   fprintf(file, "%d %d ", polyp[i].p_x, polyp[i].p_y);
	}
	fprintf(file, "%d tb\n", np);

	if (PlotPSBoundary)
	{
	    if (curLineWidth != PS_THIN) {
		fprintf(file, "l1\n");
		curLineWidth = PS_THIN;
	    }

	    /* Diagonal is always drawn */

	    for (i = 0; i < np; i++)
	    {
		j = (i + 1) % np;
		if (polyp[i].p_x != polyp[j].p_x &&
			polyp[i].p_y != polyp[j].p_y)
		{
		    fprintf(file, "%d %d %d %d ml\n", polyp[i].p_x, polyp[i].p_y,
			polyp[j].p_x, polyp[j].p_y);
		    break;
		}
	    }
	}
    }
    else
    {

	/* This tile gets "normal" processing (i.e. stippling and outlining).
	 * Clip it to the plotting area and output.
	 */

	GeoClip(&rootArea, &bbox);
	xbot = rootArea.r_xbot - bbox.r_xbot;
	width = rootArea.r_xtop - rootArea.r_xbot;
	ybot = rootArea.r_ybot - bbox.r_ybot;
	height = rootArea.r_ytop - rootArea.r_ybot;

	/* compare against last output rectangle and merge if possible */
	if ((width == curwidth) && (xbot == curxbot) && ((tmptf = (ybot == curybot
			+ curheight)) || (ybot + height == curybot)))
	{
	    curheight += height;
	    if (!tmptf) curybot = ybot;
	}
	else if ((height == curheight) && (ybot == curybot) && ((tmptf = (xbot
		== curxbot + curwidth)) || (xbot + width == curxbot)))
	{
	    curwidth += width;
	    if (!tmptf) curxbot = xbot;
	}
	else
	{
	    plotPSFlushRect(curStyle->grs_stipple);
	    curheight = height;
	    curwidth = width;
	    curxbot = xbot;
	    curybot = ybot;
	}

	if (PlotPSBoundary && (curLineWidth != PS_THIN)) {
	    fprintf(file, "l1\n");
	    curLineWidth = PS_THIN;
	}
    }

    if (!PlotPSBoundary) return 0;	/* No borders */

    /* Now output lines for any edges between material of the type
     * currently being drawn and material of other types.  This is
     * done by searching along the tile's borders for neighbors that
     * have the wrong types.  First, search the tile's bottom border
     * (unless it is at infinity).
     *
     * (This code is essentially a duplicate of selRedisplayFunc())
     */

    if (IsSplit(tile) && (!(SplitSide(tile) ^ SplitDirection(tile))))
        goto searchleft;        /* nothing on bottom of split */

    if (tileArea.r_ybot > TiPlaneRect.r_ybot)
    {
	edge.r_ybot = edge.r_ytop = tileArea.r_ybot;
	for (neighbor = LB(tile); LEFT(neighbor) < tileArea.r_xtop;
		neighbor = TR(neighbor))
	{
	    ntype = TiGetTopType(neighbor);
	    if (TTMaskHasType(&curMask, ntype)) continue;
	    edge.r_xbot = LEFT(neighbor);
	    edge.r_xtop = RIGHT(neighbor);
	    if (edge.r_xbot < tileArea.r_xbot) edge.r_xbot = tileArea.r_xbot;
	    if (edge.r_xtop > tileArea.r_xtop) edge.r_xtop = tileArea.r_xtop;
	    GeoTransRect(&scx->scx_trans, &edge, &rootArea);
	    plotPSLine(&rootArea.r_ll, &rootArea.r_ur);
	}
    }

    /* Now go along the tile's left border, doing the same thing.   Ignore
     * edges that are at infinity.
     */

searchleft:
    if (IsSplit(tile) && SplitSide(tile))
	goto searchtop;		/* Nothing on left side of tile */

    if (tileArea.r_xbot > TiPlaneRect.r_xbot)
    {
	edge.r_xbot = edge.r_xtop = tileArea.r_xbot;
	for (neighbor = BL(tile); BOTTOM(neighbor) < tileArea.r_ytop;
		neighbor = RT(neighbor))
	{
 	    ntype = TiGetRightType(neighbor);
	    if (TTMaskHasType(&curMask, ntype)) continue;
	    edge.r_ybot = BOTTOM(neighbor);
	    edge.r_ytop = TOP(neighbor);
	    if (edge.r_ybot < tileArea.r_ybot) edge.r_ybot = tileArea.r_ybot;
	    if (edge.r_ytop > tileArea.r_ytop) edge.r_ytop = tileArea.r_ytop;
	    GeoTransRect(&scx->scx_trans, &edge, &rootArea);
	    plotPSLine(&rootArea.r_ll, &rootArea.r_ur);
	}
    }

    /* Same thing for the tile's top border. */

searchtop:
    if (IsSplit(tile) && (SplitSide(tile) ^ SplitDirection(tile)))
	goto searchright;		/* Nothing on top side of tile */

    if (tileArea.r_ytop < TiPlaneRect.r_ytop)
    {
	edge.r_ybot = edge.r_ytop = tileArea.r_ytop;
	for (neighbor = RT(tile); RIGHT(neighbor) > tileArea.r_xbot;
		neighbor = BL(neighbor))
	{
	    ntype = TiGetBottomType(neighbor);
	    if (TTMaskHasType(&curMask, ntype)) continue;
	    edge.r_xbot = LEFT(neighbor);
	    edge.r_xtop = RIGHT(neighbor);
	    if (edge.r_xbot < tileArea.r_xbot) edge.r_xbot = tileArea.r_xbot;
	    if (edge.r_xtop > tileArea.r_xtop) edge.r_xtop = tileArea.r_xtop;
	    GeoTransRect(&scx->scx_trans, &edge, &rootArea);
	    plotPSLine(&rootArea.r_ll, &rootArea.r_ur);
	}
    }

    /* Finally, the right border. */

searchright:
    if (IsSplit(tile) && !SplitSide(tile))
	return 0;		/* Nothing on right side of tile */

    if (tileArea.r_xtop < TiPlaneRect.r_xtop)
    {
	edge.r_xbot = edge.r_xtop = tileArea.r_xtop;
	for (neighbor = TR(tile); TOP(neighbor) > tileArea.r_ybot;
		neighbor = LB(neighbor))
	{
	    ntype = TiGetLeftType(neighbor);
	    if (TTMaskHasType(&curMask, ntype)) continue;
	    edge.r_ybot = BOTTOM(neighbor);
	    edge.r_ytop = TOP(neighbor);
	    if (edge.r_ybot < tileArea.r_ybot) edge.r_ybot = tileArea.r_ybot;
	    if (edge.r_ytop > tileArea.r_ytop) edge.r_ytop = tileArea.r_ytop;
	    GeoTransRect(&scx->scx_trans, &edge, &rootArea);
	    plotPSLine(&rootArea.r_ll, &rootArea.r_ur);
	}
    }

    return 0;
}


/*
 * ----------------------------------------------------------------------------
 *
 * plotPSLabelPosition --
 *
 *	Determine the label position, orientation, and approximate bounding box
 *
 * ----------------------------------------------------------------------------
 */

int
plotPSLabelPosition(scx, label, x, y, p)
    SearchContext *scx;		/* Describes state of search when label
				 * was found.
				 */
    Label *label;		/* Label that was found. */
    int *x;			/* returned x position */
    int *y;			/* returned y position */
    int *p;			/* returned orientation */
{
    Rect rootArea;
    int pos;

    /* Mapping from our GEO_xxx positions to PS object types: */

    static int psPosition[] =
    {
	 5, /* CENTCENT */
	 1, /* TOPCENT */
	 0, /* TOPRIGHT */
	 4, /* CENTRIGHT */
	12, /* BOTRIGHT */
	13, /* BOTCENT */
	15, /* BOTLEFT */
	 7, /* CENTLEFT */
	 3  /* TOPLEFT */
    };

    GeoTransRect(&scx->scx_trans, &label->lab_rect, &rootArea);
    pos = GeoTransPos(&scx->scx_trans, label->lab_just);
    switch (pos)
    {
	case GEO_NORTH:
	case GEO_NORTHEAST:
	case GEO_NORTHWEST:
	    *y = (rootArea.r_ytop - bbox.r_ybot);
	    *y += delta;
	    break;

	case GEO_CENTER:
	case GEO_WEST:
	case GEO_EAST:
	    *y = (rootArea.r_ytop + rootArea.r_ybot) / 2 - bbox.r_ybot;
	    break;

	case GEO_SOUTH:
	case GEO_SOUTHEAST:
	case GEO_SOUTHWEST:
	    *y = (rootArea.r_ybot - bbox.r_ybot);
	    *y -= delta;
	    break;
    }
    switch (pos)
    {
	case GEO_WEST:
	case GEO_NORTHWEST:
	case GEO_SOUTHWEST:
	    *x = (rootArea.r_xbot - bbox.r_xbot);
	    *x -= delta;
	    break;

	case GEO_CENTER:
	case GEO_NORTH:
	case GEO_SOUTH:
	    *x = (rootArea.r_xtop + rootArea.r_xbot) / 2 - bbox.r_xbot;
	    break;

	case GEO_EAST:
	case GEO_NORTHEAST:
	case GEO_SOUTHEAST:
	    *x = (rootArea.r_xtop - bbox.r_xbot);
	    *x += delta;
	    break;
    }
    *p = psPosition[pos];
    return 0;
}


/*
 * ----------------------------------------------------------------------------
 *
 * plotPSLabelBounds --
 *
 *	Estimate the bounding box extension based on label strings.
 *	In reality, we need to know label sizes to compute the scale,
 *	and we need to know the scale to compute label sizes.  However,
 *	in practice, we can only estimate the label size anyway, so we
 *	allow for some slop and just wing it.
 *
 * ----------------------------------------------------------------------------
 */

#define AVGCHARWIDTH 0.7
#define CHARHEIGHT 1.4

int
plotPSLabelBounds(scx, label)
    SearchContext *scx;		/* Describes state of search when label
				 * was found.
				 */
    Label *label;		/* Label that was found. */
{
    int pspos;
    int ls, psxsize, psysize;
    int llx, lly, urx, ury;
    int psdelta = (int)((float)delta / fscale);

    plotPSLabelPosition(scx, label, &llx, &lly, &pspos);
    urx = (int)((float)(llx - bbox.r_xtop) / fscale);
    ury = (int)((float)(lly - bbox.r_ytop) / fscale);
    llx = (int)((float)(bbox.r_xbot - llx) / fscale);
    lly = (int)((float)(bbox.r_ybot - lly) / fscale);
    ls = strlen(label->lab_text);

    psxsize = ls * (int)((float)PlotPSLabelSize * AVGCHARWIDTH);
    psysize = (int)((float)PlotPSLabelSize * CHARHEIGHT);

    switch (pspos) {
	case 0:
	    ury += psysize + psdelta;
	    urx += psxsize + psdelta;
	    break;
	case 4:
	    ury += psysize / 2;
	    lly += psysize / 2;
	    urx += psxsize + psdelta;
	    break;
	case 12:
	    lly += psysize + psdelta;
	    urx += psxsize + psdelta;
	    break;
	case 13:
	    lly += psysize + psdelta;
	    urx += psxsize / 2;
	    llx += psxsize / 2;
	    break;
	case 15:
	    lly += psysize + psdelta;
	    llx += psxsize + psdelta;
	    break;
	case 7:
	    ury += psysize / 2;
	    lly += psysize / 2;
	    llx += psxsize + psdelta;
	    break;
	case 3:
	    ury += psysize + psdelta;
	    llx += psxsize + psdelta;
	    break;
	case 1:
	    ury += psysize + psdelta;
	    urx += psxsize / 2;
	    llx += psxsize / 2;
	    break;
	case 5:
	    ury += psysize / 2;
	    lly += psysize / 2;
	    urx += psxsize / 2;
	    llx += psxsize / 2;
	    break;
    }

    if (xpmargin < urx) xpmargin = urx;
    if (ypmargin < ury) ypmargin = ury;
    if (xnmargin < llx) xnmargin = llx;
    if (ynmargin < lly) ynmargin = lly;

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * plotPSLabelBox --
 *
 *	Output the box connected to a label
 *
 * ----------------------------------------------------------------------------
 */

int
plotPSLabelBox(scx, label)
    SearchContext *scx;		/* Describes state of search when label
				 * was found.
				 */
    Label *label;		/* Label that was found. */
{
    Rect rootArea;
    int x, y;

    GeoTransRect(&scx->scx_trans, &label->lab_rect, &rootArea);

    /* Output lines marking the label's area.  Different things are
     * done depending on whether the label is a point, a line, or an
     * area.
     */

    if (curLineWidth != PS_MEDIUM) {
	fprintf(file, "l2\n");
	curLineWidth = PS_MEDIUM;
    }

    if ((rootArea.r_xbot == rootArea.r_xtop) &&
	    (rootArea.r_ybot == rootArea.r_ytop))
    {
	/* Point label.  Output a cross. */

	x = (rootArea.r_xbot - bbox.r_xbot);
	y = (rootArea.r_ybot - bbox.r_ybot);
	fprintf(file, "%d %d %d pl\n", delta, x, y);
    }
    else if ((rootArea.r_xbot == rootArea.r_xtop) ||
	     (rootArea.r_ybot == rootArea.r_ytop))
    {
	/* Line label.  Just draw a medium-thickness line. */

	plotPSLine(&rootArea.r_ll, &rootArea.r_ur);
    }
    else
    {
	/* Rectangular.  Draw lines around the boundary. */

	plotPSRect(&rootArea, 0);
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * plotPSLabel --
 *
 * 	This procedure is invoked once for each label overlapping the
 *	area being plotted.  It generates PS output to describe
 *	the label.
 *
 * Results:
 *	Always returns 0 to keep the search from aborting.
 *
 * Side effects:
 *	PS information is output.
 *
 * ----------------------------------------------------------------------------
 */

int
plotPSLabel(scx, label)
    SearchContext *scx;		/* Describes state of search when label
				 * was found.
				 */
    Label *label;		/* Label that was found. */
{
    int x, y;
    int pspos;

    plotPSLabelPosition(scx, label, &x, &y, &pspos);

    /* Output the text for the label, if the label is within delta
     * of the area we're plotting (a large label could overlap a
     * bit of the area but stick out way off-screen too).
     */

    if ((x >= -delta) && (y >= -delta) &&
	    (x <= (bbox.r_xtop - bbox.r_xbot) + delta) &&
	    (y <= (bbox.r_ytop - bbox.r_ybot) + delta))
    {
	fprintf(file, "(%s) %d %d %d lb\n", label->lab_text, pspos, x, y);
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * plotPSCell --
 *
 * 	This procedure is invoked once for each unexpanded cell that
 *	overlaps the area being plotted.
 *
 * Results:
 *	Always returns 0 to keep the search from aborting.
 *
 * Side effects:
 *	PS information is output to describe the cell.
 *
 * ----------------------------------------------------------------------------
 */

int
plotPSCell(scx)
    SearchContext *scx;		/* Describes cell whose bbox is to
				 * be plotted.
				 */
{
    extern bool PlotShowCellNames;
    char idName[100];
    Rect rootArea;
    CellDef *def;
    int x, y;

    /* Convert the cell's bounding box to root coordinates and then
     * draw as a thick outline.
     */

    def = scx->scx_use->cu_def;
    GeoTransRect(&scx->scx_trans, &def->cd_bbox, &rootArea);
    if (curLineWidth != PS_THICK) {
	fprintf(file, "l3\n");
	curLineWidth = PS_THICK;
    }
    plotPSRect(&rootArea, 0);

    if (!PlotShowCellNames)
	return 0;

    /* Output the cell definition's name in the top of the bounding box.
     * Use a bold font (#3), in a medium size (#2).  Make sure that the
     * name's positioning point is within the area we're plotting.
     */

    x = (rootArea.r_xtop + rootArea.r_xbot - 2*bbox.r_xbot)/2;
    y = (2*rootArea.r_ytop + rootArea.r_ybot - 3*bbox.r_ybot)/3;
    if ((x >= 0) && (y >= 0) &&
	    (x <= (bbox.r_xtop - bbox.r_xbot)) &&
	    (y <= (bbox.r_ytop - bbox.r_ybot)))
    {
	fprintf(file, "f2 (%s) 5 %d %d lb\n", def->cd_name, x, y);
    }

    /* Output the cell id in the bottom of the bounding box.
     * Use an italic font (#2) in a medium size (#2).
     */

    x = (rootArea.r_xtop + rootArea.r_xbot - 2*bbox.r_xbot)/2;
    y = (rootArea.r_ytop + 2*rootArea.r_ybot - 3*bbox.r_ybot)/3;
    if ((x >= 0) && (y >= 0) &&
	    (x <= (bbox.r_xtop - bbox.r_xbot)) &&
	    (y <= (bbox.r_ytop - bbox.r_ybot)))
    {
	(void) DBPrintUseId(scx, idName, 100, TRUE);
	fprintf(file, "f3 (%s) 5 %d %d lb\n", idName, x, y);
    }

    return 0;
}


/*
 * ----------------------------------------------------------------------------
 *
 * PlotPS --
 *
 * 	This procedure generates a PS file to describe an area of
 *	a layout.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
PlotPS(fileName, scx, layers, xMask)
    char *fileName;			/* Name of PS file to write. */
    SearchContext *scx;			/* The use and area and transformation
					 * in this describe what to plot.
					 */
    TileTypeBitMask *layers;		/* Tells what layers to plot.  Only
					 * paint layers in this mask, and also
					 * expanded according to xMask, are
					 * plotted.  If L_LABELS is set, then
					 * labels on the layers are also
					 * plotted, if expanded according to
					 * xMask.  If L_CELL is set, then
					 * subcells that are unexpanded
					 * according to xMask are plotted as
					 * bounding boxes.
					 */
    int xMask;				/* An expansion mask, used to indicate
					 * the window whose expansion status
					 * will be used to determine
					 * visibility.  Zero means treat
					 * everything as expanded.
					 */
{
    int xsize, ysize;
    float yscale;
    FILE *infile;
    int i, j;
    int twidth, theight;
    char *fontptr, *fptr2, *fptr3;
    char line_in[100];

    PSReset();

    /* Compute a scale factor between our coordinates and PS
     * coordinates.
     */

    GeoTransRect(&scx->scx_trans, &scx->scx_area, &bbox);
    xsize = bbox.r_xtop - bbox.r_xbot;
    ysize = bbox.r_ytop - bbox.r_ybot;
    fscale = (float)(PlotPSWidth - 2 * PlotPSMargin) / (float)xsize;
    yscale = (float)(PlotPSHeight - 2 * PlotPSMargin) / (float)ysize;
    if (yscale < fscale) fscale = yscale;

    /* Compute a distance equal to 1/8th the size of a typical wire
     * (max of thicknesses of routing layers).  This is used to offset
     * text from labels and to compute cross size for point labels.
     */

    if (RtrMetalWidth > RtrPolyWidth)
	delta = RtrMetalWidth / 8;
    else delta = RtrPolyWidth / 8;
    if (delta == 0) delta = 1;

    /* Go through labels once to estimate the bounding box, including labels */

    xnmargin = ynmargin = xpmargin = ypmargin = 0;
    if (TTMaskHasType(layers, L_LABEL))
    {
	curMask = *layers;
	TTMaskSetType(&curMask, TT_SPACE);
	(void) DBTreeSrLabels(scx, &curMask, xMask, (TerminalPath *) NULL,
		TF_LABEL_ATTACH, plotPSLabelBounds, (ClientData) NULL);
	fscale = (float)(PlotPSWidth - 2 * PlotPSMargin - xnmargin - xpmargin)
		/ (float)(xsize);
	yscale = (float)(PlotPSHeight - 2 * PlotPSMargin - ynmargin - ypmargin)
		/ (float)(ysize);
        if (yscale < fscale) fscale = yscale;
    }
    twidth = (xsize * fscale) + xnmargin + xpmargin;
    theight = (ysize * fscale) + ynmargin + ypmargin;

    /* Open the PS file and output header information. */

    file = PaOpen(fileName, "w", (char *) NULL, ".", (char *) NULL,
	    (char **) NULL);
    if (file == NULL)
    {
	TxError("Couldn't write PS file \"%s\".\n", fileName);
	return;
    }
    fprintf(file, "%%!PS-Adobe-3.0 EPSF-3.0\n");
    fprintf(file, "%%%%BoundingBox: %d %d %d %d\n",
		PlotPSMargin, PlotPSMargin, twidth + PlotPSMargin,
		theight + PlotPSMargin);
    fontptr = PlotPSIdFont;
    fprintf(file, "%%%%DocumentNeededResources: font %s", fontptr);
    if (!Match(fptr2 = PlotPSNameFont, fontptr));
        fprintf(file, " font %s", fptr2);
    if (!Match(fptr3 = PlotPSLabelFont, fontptr))
	if (!Match(fptr3, fptr2))
            fprintf(file, " font %s", fptr3);
    fprintf(file, "\n");
    fprintf(file, "%%%%EndComments\n");

    /* Insert the prolog here */

    infile = PaOpen("magicps", "r", ".pro", ".", SysLibPath, NULL);
    if (infile != NULL)
	while(fgets(line_in, 99, infile) != NULL)
	    fputs(line_in, file);
    else
	fprintf(file, "\npostscript_prolog_is_missing\n\n");

    /* Insert the font definitions here. */

    fprintf(file, "/f1 { %.3f %s sf } def\n", (float)PlotPSLabelSize / fscale,
		PlotPSLabelFont);
    fprintf(file, "/f2 { %.3f %s sf } def\n", (float)PlotPSNameSize / fscale,
		PlotPSNameFont);
    fprintf(file, "/f3 { %.3f %s sf } def\n", (float)PlotPSIdSize / fscale,
		PlotPSIdFont);

    /* Insert the color and stipple definitions here. */

    for (curColor = plotPSColors; curColor != NULL;
	 curColor = curColor->col_next)
    {
	fprintf(file, "/col%d {%.3f %.3f %.3f %.3f sc} bind def\n",
		curColor->index,
		(float)curColor->color[0] / 255.0,
		(float)curColor->color[1] / 255.0,
		(float)curColor->color[2] / 255.0,
		(float)curColor->color[3] / 255.0);
    }

    for (curPattern = plotPSPatterns; curPattern != NULL;
	curPattern = curPattern->pat_next)
    {
	fprintf(file, "{<");
	for (j = 0; j < 8; j++)
	    fprintf(file, "%08lx%08lx", curPattern->stipple[j], curPattern->stipple[j]);
	fprintf(file, ">} %d dp\n", curPattern->index);
    }

    fprintf(file, "%%%%EndResource\n%%%%EndProlog\n\n");
    fprintf(file, "%%%%Page: 1 1\n");
    fprintf(file, "/pgsave save def bop\n");
    fprintf(file, "%% 0 0 offsets\nninit\n");
    fprintf(file, "%d %d translate\n", PlotPSMargin + xnmargin, PlotPSMargin
		+ ynmargin);
    fprintf(file, "%.3f %.3f scale\nminit\n", fscale, fscale);
    fprintf(file, "0 0 %d %d gsave rectclip\n", xsize, ysize);
    fprintf(file, "l2\nsp\n\n");

    curLineWidth = PS_MEDIUM;

    /* For each PS style, find all the paint layers that belong
     * to that style and put plot information into the file.
     */

    for (curStyle = plotPSStyles; curStyle != NULL;
	 curStyle = curStyle->grs_next)
    {
	fprintf(file, "col%d\n", curStyle->grs_color);
	if (curStyle->grs_stipple >= 0)
	    fprintf(file, "%d sl\n", curStyle->grs_stipple);
	TTMaskAndMask3(&curMask, layers, &curStyle->grs_layers);
	(void) DBTreeSrTiles(scx, &curMask, xMask, plotPSPaint,
		(ClientData) NULL);
	plotPSFlushRect(curStyle->grs_stipple);
	plotPSFlushLine();
	PSReset();
    }

    /* Output subcell bounding boxes, if they are wanted. */

    if (TTMaskHasType(layers, L_CELL))
    {
	(void) DBTreeSrCells(scx, xMask, plotPSCell, (ClientData) NULL);
	plotPSFlushRect(BORDER);
	plotPSFlushLine();
    }

    /* Output label boxes followed by labels */

    if (TTMaskHasType(layers, L_LABEL))
    {
	curMask = *layers;
	TTMaskSetType(&curMask, TT_SPACE);
	(void) DBTreeSrLabels(scx, &curMask, xMask, (TerminalPath *) NULL,
		TF_LABEL_ATTACH, plotPSLabelBox, (ClientData) NULL);
	plotPSFlushRect(BORDER);
	plotPSFlushLine();
	PSReset();
        fprintf(file, "grestore\n");	/* end clipping rectangle */
        fprintf(file, "f1 0 setgray\n");  /* set font, set color to black */

	curMask = *layers;
	TTMaskSetType(&curMask, TT_SPACE);
	(void) DBTreeSrLabels(scx, &curMask, xMask, (TerminalPath *) NULL,
		TF_LABEL_ATTACH, plotPSLabel, (ClientData) NULL);
    }
    else
    {
        fprintf(file, "grestore\n");	/* end clipping rectangle */
    }

    /* Output trailer information into the file, and close it. */

    fprintf(file, "pgsave restore showpage\n\n");
    fprintf(file, "%%%%Trailer\nMAGICsave restore\n%%%%EOF\n");
    fclose(file);
    return;
}
