/*
 * DBlabel.c --
 *
 * Label manipulation primitives.
 *
 *     *********************************************************************
 *     * 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/database/DBlabel.c,v 1.9 2010/08/25 17:33:55 tim Exp $";
#endif  /* not lint */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <math.h>	/* For sin(), cos(), and round() functions */
#include <ctype.h>

#include "utils/magic.h"
#include "utils/malloc.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "utils/utils.h"
#include "database/database.h"
#include "database/fonts.h"
#include "database/databaseInt.h"
#include "extract/extractInt.h"   /* for ExtCurStyle */
#include "windows/windows.h"
#include "dbwind/dbwind.h"
#include "commands/commands.h"
#include "textio/textio.h"

static TileType DBPickLabelLayer(/* CellDef *def, Label *lab, bool doCalma */);

/* Globally-accessible font information */

MagicFont **DBFontList = NULL;
int DBNumFonts = 0;

/*
 * ----------------------------------------------------------------------------
 *
 * DBIsSubcircuit --
 *
 * Check if any labels in a CellDef declare port attributes, indicating
 * that the CellDef should be treated as a subcircuit.
 *
 * Results:
 *	TRUE if CellDef contains ports, FALSE otherwise.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

bool
DBIsSubcircuit(cellDef)
    CellDef *cellDef;
{
    Label *lab;

    for (lab = cellDef->cd_labels; lab != NULL; lab = lab->lab_next)
	if (lab->lab_flags & PORT_DIR_MASK)
	    return TRUE;

    return FALSE;
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBPutLabel --
 *
 * Place a rectangular label in the database, in a particular cell.
 * This is a wrapper function to the more complete DBPutFontLabel()
 * (see below).
 *
 * ----------------------------------------------------------------------------
 */

Label *
DBPutLabel(cellDef, rect, align, text, type, flags, port)
    CellDef *cellDef;
    Rect *rect;
    int align;
    char *text;
    TileType type;
    unsigned short flags;
    unsigned int port;
{
    /* Draw text in a standard X11 font */
    return DBPutFontLabel(cellDef, rect, -1, 0, 0, &GeoOrigin,
		align, text, type, flags, port);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBPutFontLabel --
 *
 * Place a rectangular label in the database, in a particular cell.
 *
 * It is the responsibility of higher-level routines to insure that
 * the material to which the label is being attached really exists at
 * this point in the cell, and that TT_SPACE is used if there is
 * no single material covering the label's entire area.  The routine
 * DBAdjustLabels is useful for this.
 *
 * Results:
 *	The return value is the actual alignment position used for
 *	the label.  This may be different from align, if align is
 *	defaulted.
 *
 * Side effects:
 *	Updates the label list in the CellDef to contain the label.
 *
 * ----------------------------------------------------------------------------
 */
Label *
DBPutFontLabel(cellDef, rect, font, size, rot, offset, align, text, type, flags, port)
    CellDef *cellDef;	/* Cell in which label is placed */
    Rect *rect;		/* Location of label; see above for description */
    int font;		/* A vector outline font to use, or -1 for X11 font */
    int size;		/* Scale of vector font relative to the database (x8) */
    int rot;		/* Rotate of the vector font in degrees */
    Point *offset;	/* Offset of the font from the point of origin */
    int align;		/* Orientation/alignment of text.  If this is < 0,
			 * an orientation will be picked to keep the text
			 * inside the cell boundary.
			 */
    char *text;		/* Pointer to actual text of label */
    TileType type;	/* Type of tile to be labelled */
    unsigned short flags; /* Label flags */
    unsigned int port;	/* Port index (if label is a port, per the flags) */
{
    Label *lab;
    int len, x1, x2, y1, y2, tmp, labx, laby;

    len = strlen(text) + sizeof (Label) - sizeof lab->lab_text + 1;
    lab = (Label *) mallocMagic ((unsigned) len);
    strcpy(lab->lab_text, text);

    /* Pick a nice alignment if the caller didn't give one.  If the
     * label is more than BORDER units from an edge of the cell,
     * use GEO_NORTH.  Otherwise, put the label on the opposite side
     * from the boundary, so it won't stick out past the edge of
     * the cell boundary.
     */

#define BORDER 5
    if (align < 0)
    {
	tmp = (cellDef->cd_bbox.r_xtop - cellDef->cd_bbox.r_xbot)/3;
	if (tmp > BORDER) tmp = BORDER;
	x1 = cellDef->cd_bbox.r_xbot + tmp;
	x2 = cellDef->cd_bbox.r_xtop - tmp;
	tmp = (cellDef->cd_bbox.r_ytop - cellDef->cd_bbox.r_ybot)/3;
	if (tmp > BORDER) tmp = BORDER;
	y1 = cellDef->cd_bbox.r_ybot + tmp;
	y2 = cellDef->cd_bbox.r_ytop - tmp;
	labx = (rect->r_xtop + rect->r_xbot)/2;
	laby = (rect->r_ytop + rect->r_ybot)/2;

	if (labx <= x1)
	{
	    if (laby <= y1) align = GEO_NORTHEAST;
	    else if (laby >= y2) align = GEO_SOUTHEAST;
	    else align = GEO_EAST;
	}
	else if (labx >= x2)
	{
	    if (laby <= y1) align = GEO_NORTHWEST;
	    else if (laby >= y2) align = GEO_SOUTHWEST;
	    else align = GEO_WEST;
	}
	else
	{
	    if (laby <= y1) align = GEO_NORTH;
	    else if (laby >= y2) align = GEO_SOUTH;
	    else align = GEO_NORTH;
	}
    }

    lab->lab_just = align;
    if (font >= 0 && font < DBNumFonts)
    {
	lab->lab_font = font;
	lab->lab_size = size;
	lab->lab_rotate = (short)rot;
	lab->lab_offset = *offset;
    }
    else
    {
	lab->lab_font = -1;
	lab->lab_size = 0;
	lab->lab_rotate = (short)0;
	lab->lab_offset = GeoOrigin;
    }
    lab->lab_type = type;
    lab->lab_flags = flags;
    lab->lab_port = port;
    lab->lab_rect = *rect;
    lab->lab_next = NULL;
    if (cellDef->cd_labels == NULL)
	cellDef->cd_labels = lab;
    else
    {
	ASSERT(cellDef->cd_lastLabel->lab_next == NULL, "DBPutLabel");
	cellDef->cd_lastLabel->lab_next = lab;
    }
    cellDef->cd_lastLabel = lab;

    DBFontLabelSetBBox(lab);
    DBUndoPutLabel(cellDef, lab);
    cellDef->cd_flags |= CDMODIFIED|CDGETNEWSTAMP;
    return lab;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBEraseGlobLabel --
 *
 * Delete labels attached to tiles of the indicated types that
 * are in the given area (as determined by the macro GEO_LABEL_IN_AREA).
 * If this procedure is called as part of a command that also modifies paint,
 * then the paint modifications should be done BEFORE calling here.
 *
 * Results:
 *	TRUE if any labels were deleted, FALSE otherwise.
 *
 * Side effects:
 *	This procedure tries to be clever in order to avoid deleting
 *	labels whenever possible.  If there's enough material on the
 *	label's attached layer so that the label can stay on its
 *	current layer, or if the label can be migrated to a layer that
 *	connects to its current layer, then the label is not deleted.
 *	Deleting up to the edge of a label won't cause the label
 *	to go away.  There's one final exception:  if the mask includes
 *	L_LABEL, then labels are deleted from all layers even if there's
 *	still enough material to keep them around.  The rect pointed to
 *	by areaReturn is filled with the area affected by removing the
 *	label, for purposes of redrawing the necessary portions of the
 *	screen.
 *
 * ----------------------------------------------------------------------------
 */

bool
DBEraseGlobLabel(cellDef, area, mask, areaReturn, globmatch)
    CellDef *cellDef;		/* Cell being modified */
    Rect *area;			/* Area from which labels are to be erased.
				 * This may be a point; any labels touching
				 * or overlapping it are erased.
				 */
    TileTypeBitMask *mask;	/* Mask of types from which labels are to
				 * be erased.
				 */
    Rect *areaReturn;		/* Expand this with label bounding box */
    char *globmatch;		/* If non-NULL, do glob-style matching of
				 * any label against this string.
				 */
{
    Label *lab, *labPrev;
    bool erasedAny = FALSE;
    TileType newType;

    free_magic1_t mm1 = freeMagic1_init();
    labPrev = NULL;
    lab = cellDef->cd_labels;
    while (lab != NULL)
    {
	if (!GEO_LABEL_IN_AREA(&lab->lab_rect, area)) goto nextLab;
	if (!TTMaskHasType(mask, L_LABEL))
	{
	    if (!TTMaskHasType(mask, lab->lab_type)) goto nextLab;

	    /* Labels on space always get deleted at this point, since
	     * there's no reasonable new layer to put them on.
	     */
	    if (!(lab->lab_type == TT_SPACE))
	    {
		newType = DBPickLabelLayer(cellDef, lab, FALSE);
		if (DBConnectsTo(newType, lab->lab_type)) goto nextLab;
	    }
	}
	if (globmatch != NULL)
	{
	    if (!Match(globmatch, lab->lab_text)) goto nextLab;
	}

	DBWLabelChanged(cellDef, lab, DBW_ALLWINDOWS);
	if (labPrev == NULL)
	    cellDef->cd_labels = lab->lab_next;
	else labPrev->lab_next = lab->lab_next;
	if (cellDef->cd_lastLabel == lab)
	    cellDef->cd_lastLabel = labPrev;
	DBUndoEraseLabel(cellDef, lab);
	if ((lab->lab_font >= 0) && areaReturn)
	    GeoInclude(&lab->lab_bbox, areaReturn);

	freeMagic1(&mm1, (char *) lab);
	lab = lab->lab_next;
	erasedAny = TRUE;
	continue;

	nextLab: labPrev = lab;
	lab = lab->lab_next;
    }
    freeMagic1_end(&mm1);

    if (erasedAny)
	cellDef->cd_flags |= CDMODIFIED|CDGETNEWSTAMP;
    return (erasedAny);
}

#define	RECTEQUAL(r1, r2)	  ((r1)->r_xbot == (r2)->r_xbot \
				&& (r1)->r_ybot == (r2)->r_ybot \
				&& (r1)->r_xtop == (r2)->r_xtop \
				&& (r1)->r_ytop == (r2)->r_ytop)

/*
 * ----------------------------------------------------------------------------
 *
 * DBEraseLabel ---
 *
 *	Wrapper around DBEraseGlobLabel() with globmatch set to NULL so
 *	that labels are erased verbatim rather that being matched against
 *	a glob-style string.
 *
 * Results:
 *	Passes back the result of DBEraseGlobLabel()
 *
 * Side effects:
 *	See DBEraseGlobLabel()
 *
 * ----------------------------------------------------------------------------
 */

bool
DBEraseLabel(cellDef, area, mask, areaReturn)
    CellDef *cellDef;		/* Cell being modified */
    Rect *area;			/* Area from which labels are to be erased.
				 * This may be a point; any labels touching
				 * or overlapping it are erased.
				 */
    TileTypeBitMask *mask;	/* Mask of types from which labels are to
				 * be erased.
				 */
    Rect *areaReturn;		/* Expand this with label bounding box */
{
    return DBEraseGlobLabel(cellDef, area, mask, areaReturn, NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBCheckLabelsByContent --
 *
 * Return any label found on the label list for the given
 * CellDef that matches the given specification.
 *
 * Results:
 *	Returns a label if a match is found, otherwise returns NULL.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

Label *
DBCheckLabelsByContent(def, rect, type, text)
    CellDef *def;		/* Where to look for label to delete. */
    Rect *rect;			/* Coordinates of label.  If NULL, then
				 * labels are searched regardless of coords.
				 */
    TileType type;		/* Layer label is attached to.  If < 0, then
				 * labels are searched regardless of type.
				 */
    char *text;			/* Text associated with label.  If NULL, then
				 * labels are searched regardless of text.
				 */
{
    Label *lab;

    for (lab = def->cd_labels; lab; lab = lab->lab_next)
    {
	if ((rect != NULL) && !(RECTEQUAL(&lab->lab_rect, rect))) continue;
	if ((type >= 0) && (type != lab->lab_type)) continue;
	if ((text != NULL) && (strcmp(text, lab->lab_text) != 0)) continue;

	return lab;
    }
    return NULL;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBEraseLabelsByContent --
 *
 * Erase any labels found on the label list for the given
 * CellDef that match the given specification.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the label list for the argument CellDef.  The
 *	DBWind module is notified about any labels that were
 *	deleted.
 *
 * ----------------------------------------------------------------------------
 */

void
DBEraseLabelsByContent(def, rect, type, text)
    CellDef *def;		/* Where to look for label to delete. */
    Rect *rect;			/* Coordinates of label.  If NULL, then
				 * labels are deleted regardless of coords.
				 */
    TileType type;		/* Layer label is attached to.  If < 0, then
				 * labels are deleted regardless of type.
				 */
    char *text;			/* Text associated with label.  If NULL, then
				 * labels are deleted regardless of text.
				 */
{
    Label *lab, *labPrev;

    free_magic1_t mm1 = freeMagic1_init();
    for (labPrev = NULL, lab = def->cd_labels;
	    lab != NULL;
	    labPrev = lab, lab = lab->lab_next)
    {
	nextCheck:
	if ((rect != NULL) && !(RECTEQUAL(&lab->lab_rect, rect))) continue;
	if ((type >= 0) && (type != lab->lab_type)) continue;
	if ((text != NULL) && (strcmp(text, lab->lab_text) != 0)) continue;
	DBUndoEraseLabel(def, lab);
	DBWLabelChanged(def, lab, DBW_ALLWINDOWS);
	if (labPrev == NULL)
	    def->cd_labels = lab->lab_next;
	else labPrev->lab_next = lab->lab_next;
	if (def->cd_lastLabel == lab)
	    def->cd_lastLabel = labPrev;
	freeMagic1(&mm1, (char *) lab);

	/* Don't iterate through loop, since this will skip a label:
	 * just go back to top.  This is tricky!
	 */

	lab = lab->lab_next;
	if (lab == NULL) break;
	else goto nextCheck;
    }
    freeMagic1_end(&mm1);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBRemoveLabel --
 *
 * Erase a labels by reference.
 * TRUE.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the label list for the argument CellDef.  The
 *	DBWind module is notified about any labels that were
 *	deleted.
 *
 * ----------------------------------------------------------------------------
 */

void
DBRemoveLabel(def, refLab)
    CellDef *def;		/* Where to look for label to delete. */
    Label *refLab;
{
    Label *lab, *labPrev;

    free_magic1_t mm1 = freeMagic1_init();
    for (labPrev = NULL, lab = def->cd_labels;
	    lab != NULL;
	    labPrev = lab, lab = lab->lab_next)
    {
	nextCheck:
	if (lab != refLab) continue;
	DBUndoEraseLabel(def, lab);
	DBWLabelChanged(def, lab, DBW_ALLWINDOWS);
	if (labPrev == NULL)
	    def->cd_labels = lab->lab_next;
	else labPrev->lab_next = lab->lab_next;
	if (def->cd_lastLabel == lab)
	    def->cd_lastLabel = labPrev;
	freeMagic1(&mm1, (char *) lab);

	/* Don't iterate through loop, since this will skip a label:
	 * just go back to top.  This is tricky!
	 */

	lab = lab->lab_next;
	if (lab == NULL) break;
	else goto nextCheck;
    }
    freeMagic1_end(&mm1);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBReOrientLabel --
 *
 * 	Change the text positions of all labels underneath a given
 *	area in a given cell.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
DBReOrientLabel(cellDef, area, newPos)
    CellDef *cellDef;		/* Cell whose labels are to be modified. */
    Rect *area;			/* All labels touching this area have their
				 * text positions changed.
				 */
    int newPos;			/* New text positions for all labels in
				 * the area, for example, GEO_NORTH.
				 */
{
    Label *lab;

    for (lab = cellDef->cd_labels; lab != NULL; lab = lab->lab_next)
    {
	if (GEO_TOUCH(area, &lab->lab_rect))
	{
	    DBUndoEraseLabel(cellDef, lab);
	    DBWLabelChanged(cellDef, lab, DBW_ALLWINDOWS);
	    lab->lab_just = newPos;
	    DBUndoPutLabel(cellDef, lab);
	    DBWLabelChanged(cellDef, lab, DBW_ALLWINDOWS);
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbGetLabelArea ---
 *
 * Callback function used by DBAdjustLabels.  Find all material under a label
 * that is *not* the label type, and return the label area adjusted to leave
 * out that amount.
 *
 * Note:  This clips in a regular order, and does not consider what is the
 * largest rectangular area outside the area that has been clipped out.
 *
 * ----------------------------------------------------------------------------
 */

int
dbGetLabelArea(tile, dinfo, area)
    Tile *tile;		/* Tile found. */
    TileType dinfo;	/* Split tile information (unused) */
    Rect *area;		/* Area to be modified. */
{
    Rect r;
    
    TiToRect(tile, &r);

    if (r.r_xbot > area->r_xbot)
	area->r_xtop = r.r_xbot;
    else if (r.r_xtop < area->r_xtop)
	area->r_xbot = r.r_xtop;
    else if (r.r_ybot > area->r_ybot)
	area->r_ytop = r.r_ybot;
    else if (r.r_ytop < area->r_ytop)
	area->r_ybot = r.r_ytop;

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbLabelNotEmpty ---
 *
 * Callback function used by DBAdjustLabels.  Finds any material under a
 * label that is the label type, and returns 1 to stop the search.
 *
 * ----------------------------------------------------------------------------
 */

int
dbLabelNotEmpty(tile, dinfo, clientData)
    Tile *tile;		/* Tile found. */
    TileType dinfo;	/* Split tile information (unused) */
    ClientData clientData;	/* (unused) */
{
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBAdjustLabels --
 *
 * 	This procedure is called after paint has been modified
 *	in an area.  It finds all labels overlapping that area,
 *	and adjusts the layers they are attached to to reflect
 *	the changes in paint.  Thus, a layer will automatically
 *	migrate from poly to poly-metal-contact and back to
 *	poly if the contact layer is painted and then erased.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The layer attachments of labels may change.  For each
 *	such change, a message is output.
 *
 * ----------------------------------------------------------------------------
 */

void
DBAdjustLabels(def, area)
    CellDef *def;		/* Cell whose paint was changed. */
    Rect *area;			/* Area where paint was modified. */
{
    Label *lab;
    TileType newType;
    bool modified = FALSE, adjusted = FALSE;

    /* First, find each label that crosses the area we're
     * interested in.
     */

    for (lab = def->cd_labels; lab != NULL; lab = lab->lab_next)
    {
	if (!GEO_TOUCH(&lab->lab_rect, area)) continue;
	newType = DBPickLabelLayer(def, lab, FALSE);
	if (newType == lab->lab_type) continue;
	if (lab->lab_flags & LABEL_STICKY) continue;

	/* New behavior (5/2024) (idea from Philipp Guhring)---If the new
	 * type is space, then instead of immediately casting the label off
	 * of its material, find the amount of the label that is still
	 * covered by the material.  If the material covers more than half
	 * the label area, then adjust the label area to match the material.
	 */

	adjusted = FALSE;
	if (newType == TT_SPACE)
	{
	    Rect r;
	    TileTypeBitMask lmask;

	    TTMaskSetOnlyType(&lmask, lab->lab_type);
	    /* To do:  Add compatible types (contact, residue) */

	    /* If there is no material left inside the label area, then
	     * the label gets reassigned to space.
	     */
	    if (DBSrPaintArea((Tile *) NULL, def->cd_planes[DBPlane(lab->lab_type)],
		    &lab->lab_rect, &lmask, dbLabelNotEmpty, (ClientData)NULL) == 1)
	    {
		TTMaskCom(&lmask);

		r = lab->lab_rect;
		DBSrPaintArea((Tile *) NULL, def->cd_planes[DBPlane(lab->lab_type)],
		    	&lab->lab_rect, &lmask, dbGetLabelArea, (ClientData) &r);

		if (!GEO_RECTNULL(&r))
		{
		    if ((DBVerbose >= DB_VERBOSE_ALL) &&
				((def->cd_flags & CDINTERNAL) == 0))
		    {
			TxPrintf("Adjusting size of label \"%s\" in cell %s.\n",
				lab->lab_text, def->cd_name);
		    }

		    DBUndoEraseLabel(def, lab);
		    DBWLabelChanged(def, lab, DBW_ALLWINDOWS);
		    lab->lab_rect = r;
		    DBFontLabelSetBBox(lab);
		    DBUndoPutLabel(def, lab);
		    DBWLabelChanged(def, lab, DBW_ALLWINDOWS);
		    modified = TRUE;
		    adjusted = TRUE;
		}
	    }
	}

	if (!adjusted)
	{
	    if ((DBVerbose >= DB_VERBOSE_ALL) && ((def->cd_flags & CDINTERNAL) == 0))
	    {
		TxPrintf("Moving label \"%s\" from %s to %s in cell %s.\n",
			lab->lab_text, DBTypeLongName(lab->lab_type),
			DBTypeLongName(newType), def->cd_name);
	    }
	    DBUndoEraseLabel(def, lab);
	    lab->lab_type = newType;
	    DBUndoPutLabel(def, lab);
	    modified = TRUE;
	}
    }

    if (modified) DBCellSetModified(def, TRUE);
}


/*
 *---------------------------------------------------------------
 *
 * DBAdjustLabelsNew--
 *
 *	Modified version of DBAdjustLabels, used with reading
 *	GDS and LEF files.  Since those files use GDS layers and
 *	not magic layers, there is no exact relationship between
 *	the two.  Instead, the cifinput rules are used to set an
 *	initial type only.  The rules for label adjustment are
 *	different from DBAdjustLabels(), and so a different
 *	routine is called.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The layer attachments of labels may change.  For each
 *	such change, a message is output.
 *
 *---------------------------------------------------------------
 */

void
DBAdjustLabelsNew(def, area)
    CellDef *def;	/* Cell whose paint was changed. */
    Rect *area;		/* Area where paint was modified. */
{
    Label *lab, *labPrev;
    TileType newType;
    bool modified = FALSE;

    /* First, find each label that crosses the area we're
     * interested in.
     */

    labPrev = NULL;
    lab = def->cd_labels;
    while (lab != NULL)
    {
	bool doCalma = TRUE;
	if (!GEO_TOUCH(&lab->lab_rect, area)) goto nextLab;
	if (lab->lab_type == TT_SPACE) doCalma = FALSE;
	newType = DBPickLabelLayer(def, lab, doCalma);
	if (newType == lab->lab_type)
	    goto nextLab;

	if (((newType < 0) || (newType == TT_SPACE)) && !(lab->lab_flags & LABEL_STICKY))
	{
	    if (lab->lab_type == TT_SPACE)
	    {
		TxPrintf("Deleting unattached label \"%s\" in cell %s.\n",
		 	lab->lab_text, def->cd_name);

		if (labPrev == NULL)
		    def->cd_labels = lab->lab_next;
		else
		    labPrev->lab_next = lab->lab_next;
		if (def->cd_lastLabel == lab)
		    def->cd_lastLabel = labPrev;
		DBUndoEraseLabel(def, lab);
		DBWLabelChanged(def, lab, DBW_ALLWINDOWS);
		free_magic1_t mm1 = freeMagic1_init();
		freeMagic1(&mm1, (char *) lab);
		lab = lab->lab_next;
		freeMagic1_end(&mm1);
		modified = TRUE;
		continue;
	    }
	    else
	    {
		TxPrintf("Making label \"%s\" on type %s in cell %s sticky.\n",
		 	lab->lab_text, DBTypeLongName(lab->lab_type),
		 	def->cd_name);
		lab->lab_flags |= LABEL_STICKY;
	    }
	}
	else if (!(lab->lab_flags & LABEL_STICKY))
	{
	    if ((DBVerbose >= DB_VERBOSE_ALL) && ((def->cd_flags & CDINTERNAL) == 0))
	    {
		TxPrintf("Moving label \"%s\" from %s to %s in cell %s.\n",
				lab->lab_text, DBTypeLongName(lab->lab_type),
				DBTypeLongName(newType), def->cd_name);
	    }
	    DBUndoEraseLabel(def, lab);
	    lab->lab_type = newType;
	    DBUndoPutLabel(def, lab);
	    modified = TRUE;
	}
nextLab:
	labPrev = lab;
	lab = lab->lab_next;
    }

    if (modified) DBCellSetModified(def, TRUE);
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBPickLabelLayer --
 *
 * 	This procedure looks at the material around a label and
 *	picks a new layer for the label to be attached to.
 *
 * Results:
 *	Returns a tile type, which is a layer that completely
 *	covers the label's area.  If possible, the label's current
 *	layer is chosen, or if that's not possible, then a layer
 *	that connects to the label's current layer, or else any
 *	other mask layer.  If everything fails, TT_SPACE is returned.
 *
 * Side effects:
 *	None.  The label's layer is not changed by this procedure.
 *
 * ----------------------------------------------------------------------------
 */

/* The following variable(s) are shared between DBPickLabelLayer and
 * its search function dbPickFunc2.
 */

TileTypeBitMask *dbAdjustPlaneTypes;	/* Mask of all types in current
					 * plane being searched.
					 */
TileType
DBPickLabelLayer(def, lab, doCalma)
    CellDef *def;		/* Cell definition containing label. */
    Label *lab;			/* Label for which a home must be found. */
    bool doCalma;        	/* if TRUE, use rules for GDS and LEF */
{
    TileTypeBitMask types[3], types2[3];
    Rect check1, check2;
    int i, j, plane;
    TileType choice1, choice2, choice3, choice4, choice5, choice6;
    extern int dbPickFunc1(), dbPickFunc2();

    /* Compute an array of three tile type masks.  The first is for
     * all of the types that are present everywhere underneath the
     * label. The second is for all types that are components of
     * tiles that completely cover the label's area.  The third
     * is for tile types that touch the label anywhere.  To make this
     * work correctly we have to consider four cases separately:
     * point labels, horizontal line labels, vertical line labels,
     * and rectangular labels.
     */

    if ((lab->lab_rect.r_xbot == lab->lab_rect.r_xtop)
	    && (lab->lab_rect.r_ybot == lab->lab_rect.r_ytop))
    {
	/* Point label.  Find out what layers touch the label and
	 * use this for all three masks.
	 */

	GEO_EXPAND(&lab->lab_rect, 1, &check1);
	types[0] = DBZeroTypeBits;
	for (i = PL_SELECTBASE; i < DBNumPlanes; i += 1)
	{
	    (void) DBSrPaintArea((Tile *) NULL, def->cd_planes[i],
		    &check1, &DBAllTypeBits, dbPickFunc1,
		    (ClientData) &types[0]);
	}
	types[1] = types[0];
	types[2] = types[0];
    }
    else if (lab->lab_rect.r_xbot == lab->lab_rect.r_xtop)
    {
	/* Vertical line label.  Search two areas, one on the
	 * left and one on the right.  For each side, compute
	 * the type arrays separately.  Then merge them together.
	 */

	check1 = lab->lab_rect;
	check2 = lab->lab_rect;
	check1.r_xbot -= 1;
	check2.r_xtop += 1;

	twoAreas:
	types[0] = DBAllButSpaceAndDRCBits;
	types[1] = DBAllButSpaceAndDRCBits;
	TTMaskZero(&types[2]);
	types2[0] = DBAllButSpaceAndDRCBits;
	types2[1] = DBAllButSpaceAndDRCBits;
	TTMaskZero(&types2[2]);
	for (i = PL_SELECTBASE; i < DBNumPlanes; i += 1)
	{
	    dbAdjustPlaneTypes = &DBPlaneTypes[i];
	    (void) DBSrPaintArea((Tile *) NULL, def->cd_planes[i],
		    &check1, &DBAllTypeBits, dbPickFunc2,
		    (ClientData) types);
	    (void) DBSrPaintArea((Tile *) NULL, def->cd_planes[i],
		    &check2, &DBAllTypeBits, dbPickFunc2,
		    (ClientData) types2);
	}
	TTMaskSetMask(&types[0], &types2[0]);
	TTMaskSetMask(&types[1], &types2[1]);
	TTMaskSetMask(&types[2], &types2[2]);
    }
    else if (lab->lab_rect.r_ybot == lab->lab_rect.r_ytop)
    {
	/* Horizontal line label.  Search two areas, one on the
	 * top and one on the bottom.  Use the code from above
	 * to handle.
	 */

	check1 = lab->lab_rect;
	check2 = lab->lab_rect;
	check1.r_ybot -= 1;
	check2.r_ytop += 1;
	goto twoAreas;
    }
    else
    {
	/* This is a rectangular label.  Same thing as for line labels,
	 * except there's only one area to search.
	 */

	types[0] = DBAllButSpaceAndDRCBits;
	types[1] = DBAllButSpaceAndDRCBits;
	TTMaskZero(&types[2]);
	for (i = PL_SELECTBASE; i < DBNumPlanes; i += 1)
	{
	    dbAdjustPlaneTypes = &DBPlaneTypes[i];
	    (void) DBSrPaintArea((Tile *) NULL, def->cd_planes[i],
		    &lab->lab_rect, &DBAllTypeBits, dbPickFunc2,
		    (ClientData) types);
	}
    }

    /* If the label's layer covers the label's area, use it.
     * Otherwise, look for a layer in the following order
     * (Note:  "covers" means covers the area of the label if the
     * label has area or touches the entire label if it doesn't.):
     *
     * If "doCalma" is TRUE, then:
     *   1. A layer on the same plane as the original layer and that
     *      covers the label and is not a device type.
     *   2. A layer on the same plane as the original layer and that
     *      covers the label.
     *   3. A layer on the same plane as the original layer and that
     *      is a component of material that covers the label.
     *   4. -1
     *
     * If "doCalma" is FALSE, then:
     *   1. A layer on the same plane as the original layer and that
     *      covers the label and connects to its original layer.
     *   2. A layer on the same plane as the original layer and that
     *      is a component of material that covers the label and
     *      connects to its original layer.
     *   3. A layer that covers the label and connects to the old layer.
     *   4. A layer that is a component of material that covers the
     *      label and connects to the old layer.
     *   5. A layer that covers the label and is on the same plane as the
     *	    label.
     *   6. A layer that is a component of material that covers the label
     *	    and is on the same plane as the label.
     *   7. Space.
     *
     * All searches are done from the lowest to highest plane, so that
     * the label connects to material on the highest plane that matches
     * the criteria above.  This avoids weirdnesses caused by declaring
     * types out of order in the techfile.
     *
     * Note that when the "label" command is used with no type given,
     * then lab_type is TT_SPACE and "same plane as the label" is all
     * planes.  This should never be true when called during GDS or
     * LEF reads, so only needs to be checked when "doCalma" is FALSE.
     */

    if (TTMaskHasType(&types[0], lab->lab_type)) return lab->lab_type;
    plane = DBPlane(lab->lab_type);

    if (doCalma)
    {
	choice1 = choice2 = choice3 = -1;

	for (i = TT_SELECTBASE; i < DBNumUserLayers; i += 1)
	{
	    if (!TTMaskHasType(&DBPlaneTypes[plane], i)) continue;

	    if (TTMaskHasType(&types[0], i) && (ExtCurStyle != NULL))
	    {
		if (TTMaskHasType(&ExtCurStyle->exts_deviceMask, i))
		    choice2 = i;
		else
		    choice1 = i;
		continue;
	    }
	    else if (TTMaskHasType(&types[1], i))
	    {
		choice3 = i;
		continue;
	    }
	}

	if (choice1 != -1) return choice1;
	else if (choice2 != -1) return choice2;
	else return choice3;
    }
    else
    {
	choice1 = choice2 = choice3 = choice4 = choice5 = choice6 = TT_SPACE;

	for (j = PL_SELECTBASE; j < DBNumPlanes; j++)
	{
	    for (i = TT_SELECTBASE; i < DBNumUserLayers; i += 1)
	    {
		if (!TTMaskHasType(&DBPlaneTypes[j], i)) continue;

		if (DBConnectsTo(i, lab->lab_type))
		{
		    if (DBPlane(i) == plane)
		    {
			if (TTMaskHasType(&types[0], i))
			{
			    choice1 = i;
			    continue;
			}
			else if (TTMaskHasType(&types[1], i))
			{
			    choice2 = i;
			    continue;
			}
		    }
		    if (TTMaskHasType(&types[0], i))
		    {
			choice3 = i;
			continue;
		    }
		    else if (TTMaskHasType(&types[1], i))
		    {
			choice4 = i;
			continue;
		    }
		}
		if ((DBPlane(i) == plane) || (lab->lab_type == TT_SPACE))
		{
		    if (TTMaskHasType(&types[0], i))
		    {
			/* A type that connects to more than itself is preferred */
			if (choice5 == TT_SPACE)
			    choice5 = i;
			else
			{
			    TileTypeBitMask ctest;
			    TTMaskZero(&ctest);
			    TTMaskSetMask(&ctest, &DBConnectTbl[i]);
			    TTMaskClearType(&ctest, i);
			    if (!TTMaskIsZero(&ctest))
				choice5 = i;
			    else if (TTMaskHasType(&types[1], i))
				choice6 = i;
			}
			continue;
		    }
		    else if (TTMaskHasType(&types[1], i))
		    {
			choice6 = i;
			continue;
		    }
		}
	    }
	}

	if (choice1 != TT_SPACE) return choice1;
	else if (choice2 != TT_SPACE) return choice2;
	else if (choice3 != TT_SPACE) return choice3;
	else if (choice4 != TT_SPACE) return choice4;
	else if (choice5 != TT_SPACE) return choice5;
	else return choice6;
    }
}

/* Search function for DBPickLabelLayer:  just OR in the type of
 * any tiles (except space) to the mask passed as clientdata.
 * Always return 0 to keep the search alive.
 */

int
dbPickFunc1(tile, dinfo, mask)
    Tile *tile;			/* Tile found. */
    TileType dinfo;		/* Split tile information */
    TileTypeBitMask *mask;	/* Mask to be modified. */
{
    TileType type;

    if (IsSplit(tile))
	type = (dinfo & TT_SIDE) ? SplitRightType(tile) : SplitLeftType(tile);
    else
	type = TiGetType(tile);

    if (type == TT_SPACE) return 0;
    TTMaskSetType(mask, type);
    return 0;
}

/* Another search function for DBPickLabelLayer.  For the first element
 * in the mask array, AND off all types on the current plane except the
 * given type.  For the second element, AND off all types on the current
 * plane except the ones that are components of this tile's type.  For
 * the third element of the array, just OR in the type of the current
 * tile.  A space tile ruins the whole plane so return 1 to abort the
 * search.  Otherwise return 0.
 */

int
dbPickFunc2(tile, dinfo, mask)
    Tile *tile;			/* Tile found. */
    TileType dinfo;		/* Split tile information */
    TileTypeBitMask *mask;	/* Mask to be modified. */
{
    TileType type;
    TileTypeBitMask tmp, *rMask;

    if (IsSplit(tile))
	type = (dinfo & TT_SIDE) ? SplitRightType(tile) : SplitLeftType(tile);
    else
	type = TiGetType(tile);

    if (type == TT_SPACE)
    {
	/* Space means can't have any tile types on this plane. */

	TTMaskClearMask(&mask[0], dbAdjustPlaneTypes);
	TTMaskClearMask(&mask[1], dbAdjustPlaneTypes);
	return 1;
    }

    tmp = *dbAdjustPlaneTypes;
    TTMaskClearType(&tmp, type);
    TTMaskClearMask(&mask[0], &tmp);
    rMask = DBResidueMask(type);
    TTMaskClearMask(&tmp, rMask);
    TTMaskClearMask(&mask[1], &tmp);
    TTMaskSetType(&mask[2], type);
    return 0;
}

/*
 *  Linked point list, used as temporary space when generating
 *  font character paths.
 */

typedef struct fontpath *FontPathPtr;
typedef struct fontpath
{
    Point       fp_point;
    FontPathPtr fp_next;
} FontPath;

#define INTSEGS 5	/* Font outline Bezier curves are deconstructed
			 * into (INTSEGS + 1) line segments
			 */
float par[INTSEGS];
float parsq[INTSEGS];
float parcb[INTSEGS];

/*
 * ----------------------------------------------------------------------------
 *
 * Initialize Bezier parametric settings
 *
 * ----------------------------------------------------------------------------
 */

void
DBFontInitCurves()
{
    float t;
    short idx;

    for (idx = 0; idx < INTSEGS; idx++)
    {
	t = (float)(idx + 1) / (INTSEGS + 1);
	par[idx] = t;
	parsq[idx] = t * t;
	parcb[idx] = parsq[idx] * t;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 *  Compute line segment points from control points of a Cubic Bezier curve.
 *
 *  Note:  This routine does not take into account the curvature, and therefore
 *  does not optimize for the minimum number of line segments needed to
 *  represent the curve within a fixed margin of error.
 *
 * ----------------------------------------------------------------------------
 */

void
CalcBezierPoints(fp, bp)
    FontPath *fp;		/* Pointer to last point of closed path */
    FontPath *bp;		/* Pointer to 1st of 3 bezier control points */
{
    FontPath *newPath, *curPath;
    Point *beginPath, *ctrl1, *ctrl2, *endPath;
    float ax, bx, cx, ay, by, cy;
    int idx, tmpx, tmpy;

    beginPath = &fp->fp_point;
    ctrl1 = &fp->fp_next->fp_point;
    ctrl2 = &fp->fp_next->fp_next->fp_point;
    endPath = &fp->fp_next->fp_next->fp_next->fp_point;

    cx = 3.0 * (float)(ctrl1->p_x - beginPath->p_x);
    bx = 3.0 * (float)(ctrl2->p_x - ctrl1->p_x) - cx;
    ax = (float)(endPath->p_x - beginPath->p_x) - cx - bx;

    cy = 3.0 * (float)(ctrl1->p_y - beginPath->p_y);
    by = 3.0 * (float)(ctrl2->p_y - ctrl1->p_y) - cy;
    ay = (float)(endPath->p_y - beginPath->p_y) - cy - by;

    curPath = fp;
    for (idx = 0; idx < INTSEGS; idx++)
    {
	tmpx = (int)(ax * parcb[idx] + bx * parsq[idx] +
		cx * par[idx] + beginPath->p_x);
	tmpy = (int)(ay * parcb[idx] + by * parsq[idx] +
		cy * par[idx] + beginPath->p_y);
	if ((tmpx != curPath->fp_point.p_x) || (tmpy != curPath->fp_point.p_y))
	{
	    newPath = (FontPath *)mallocMagic(sizeof(FontPath));
	    newPath->fp_point.p_x = tmpx;
	    newPath->fp_point.p_y = tmpy;
	    curPath->fp_next = newPath;
	    curPath = newPath;
	}
    }

    /* Link the last point to the end and free the two control points */
    curPath->fp_next = bp->fp_next->fp_next;
    freeMagic(bp->fp_next);
    freeMagic(bp);
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbGetToken ---
 *
 *	Tokenize PostScript input from a Type3 font definition file.
 *
 * ----------------------------------------------------------------------------
 */

char *
dbGetToken(ff)
    FILE *ff;
{
    static char line[512];
    static char *lineptr = NULL;
    char *rptr;

    while (lineptr == NULL)
    {
	if (fgets(line, 511, ff) == NULL)
	    return NULL;
	else
	{
	    lineptr = line;
	    while (isspace(*lineptr)) lineptr++;
	}
	if (*lineptr == '%') lineptr = NULL;	 /* Skip comment lines */
	else if (*lineptr == '\n') lineptr = NULL;   /* Skip blank lines */
    }

    rptr = lineptr;

    while (!isspace(*lineptr) && (*lineptr != '\n')) lineptr++;
    if (*lineptr == '\n')
    {
	*lineptr = '\0';
	lineptr = NULL;
    }
    else
    {
	*lineptr = '\0';
	lineptr++;
	while (isspace(*lineptr)) lineptr++;
    }
    return rptr;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBFontLabelSetBBox --
 *
 *	Given a font label with corresponding size, rotation, offset, etc.,
 *	(1) compute the four corners of the label, and (2) compute the
 *	Manhattan bounding box of the label.  The corners are computed in
 *	units of (database / 8) for a finer resolution, and measured
 *	relative to the label rectangle's origin so we don't run out of
 *	bits in computing the corner positions.
 *
 *	This routine needs to be run whenever a font label changes
 *	properties (including when the font label is created).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The label's lab_bbox record is modified to contain the bounding
 *	box, in database coordinates, of the given label.  The label's
 *	lab_corners[] points are modified to contain the corner positions
 *	of the label, in units as described above.
 *
 * ----------------------------------------------------------------------------
 */

void
DBFontLabelSetBBox(label)
    Label *label;
{
    char *tptr;
    Point *coffset, rcenter;
    Rect *cbbox, locbbox, *frect;
    Transform labTransform = GeoIdentityTransform;
    int i, ysave, size;
    double rrad, cr, sr, tmpx, tmpy;
    bool expand;

    if (label->lab_font < 0) return;	/* No action for default font */

    /* Label size is computed such that size = 1 means that the font	*/
    /* height extent is equal to 1 database unit.  The font height	*/
    /* extent is measured from the baseline up.				*/

    frect = &DBFontList[label->lab_font]->mf_extents;

    /* Get label size from outline fonts */

    locbbox = GeoNullRect;
    for (tptr = label->lab_text; *tptr != '\0'; tptr++)
    {
	DBFontChar(label->lab_font, *tptr, NULL, &coffset, &cbbox);
	if (*(tptr + 1) == '\0')
	    locbbox.r_xtop += cbbox->r_xtop;
	else
	    locbbox.r_xtop += coffset->p_x;
	locbbox.r_ytop = MAX(locbbox.r_ytop, cbbox->r_ytop);
	locbbox.r_ybot = MIN(locbbox.r_ybot, cbbox->r_ybot);
    }

    /* In case the font did not declare its extents, use the actual text height */
    locbbox.r_ytop = MAX(locbbox.r_ytop, frect->r_ytop);

    /* Scale to font size, in database units / 8 (upshift 3 bits)  */

    ysave = locbbox.r_ytop;

    locbbox.r_xbot *= label->lab_size;
    locbbox.r_xtop *= label->lab_size;
    locbbox.r_ybot *= label->lab_size;
    locbbox.r_ytop *= label->lab_size;

    locbbox.r_xbot /= ysave;
    locbbox.r_xtop /= ysave;
    locbbox.r_ybot /= ysave;
    locbbox.r_ytop /= ysave;

    /* Apply justification */

    switch (label->lab_just)
    {
	case GEO_SOUTH:
	case GEO_SOUTHWEST:
	case GEO_SOUTHEAST:
	    locbbox.r_ybot -= locbbox.r_ytop;
	    locbbox.r_ytop = 0;
	    break;
	case GEO_WEST:
	case GEO_EAST:
	case GEO_CENTER:
	    locbbox.r_ytop >>= 1;
	    locbbox.r_ybot -= locbbox.r_ytop;
	    break;
    }
    switch (label->lab_just)
    {
	case GEO_WEST:
	case GEO_NORTHWEST:
	case GEO_SOUTHWEST:
	    locbbox.r_xbot -= locbbox.r_xtop;
	    locbbox.r_xtop = 0;
	    break;
	case GEO_NORTH:
	case GEO_SOUTH:
	case GEO_CENTER:
	    locbbox.r_xtop >>= 1;
	    locbbox.r_xbot -= locbbox.r_xtop;
	    break;
    }

    /* Apply offset (already in units / 8) */

    locbbox.r_xbot += label->lab_offset.p_x;
    locbbox.r_xtop += label->lab_offset.p_x;
    locbbox.r_ybot += label->lab_offset.p_y;
    locbbox.r_ytop += label->lab_offset.p_y;

    /* Apply rotation */

    if (label->lab_rotate < 0) label->lab_rotate += 360;
    if (label->lab_rotate >= 360) label->lab_rotate -= 360;

    label->lab_corners[0] = locbbox.r_ll;
    label->lab_corners[1].p_x = locbbox.r_ur.p_x;
    label->lab_corners[1].p_y = locbbox.r_ll.p_y;
    label->lab_corners[2] = locbbox.r_ur;
    label->lab_corners[3].p_x = locbbox.r_ll.p_x;
    label->lab_corners[3].p_y = locbbox.r_ur.p_y;

    rrad = (double)label->lab_rotate * 0.0174532925;
    cr = cos(rrad);
    sr = sin(rrad);

    for (i = 0; i < 4; i++)
    {
	tmpx = label->lab_corners[i].p_x * cr - label->lab_corners[i].p_y * sr;
	tmpy = label->lab_corners[i].p_x * sr + label->lab_corners[i].p_y * cr;
	label->lab_corners[i].p_x = (int)round(tmpx);
	label->lab_corners[i].p_y = (int)round(tmpy);

	/* Initialize bounding box */
	if (i == 0)
	    label->lab_bbox.r_ll = label->lab_bbox.r_ur = label->lab_corners[0];
	else
	    GeoIncludePoint(&label->lab_corners[i], &label->lab_bbox);
    }

    /* Compute the bounding box, in (rounded) database coordinates */

    rcenter.p_x = (label->lab_rect.r_xtop + label->lab_rect.r_xbot) << 2;
    rcenter.p_y = (label->lab_rect.r_ytop + label->lab_rect.r_ybot) << 2;
    label->lab_bbox.r_xbot += rcenter.p_x;
    label->lab_bbox.r_xtop += rcenter.p_x;
    label->lab_bbox.r_ybot += rcenter.p_y;
    label->lab_bbox.r_ytop += rcenter.p_y;

    expand = (label->lab_bbox.r_xbot & 0x7f) ? TRUE : FALSE;
    label->lab_bbox.r_xbot >>= 3;
    if (expand) label->lab_bbox.r_xbot--;
    expand = (label->lab_bbox.r_xtop & 0x7f) ? TRUE : FALSE;
    label->lab_bbox.r_xtop >>= 3;
    if (expand) label->lab_bbox.r_xtop++;
    expand = (label->lab_bbox.r_ybot & 0x7f) ? TRUE : FALSE;
    label->lab_bbox.r_ybot >>= 3;
    if (expand) label->lab_bbox.r_ybot--;
    expand = (label->lab_bbox.r_ytop & 0x7f) ? TRUE : FALSE;
    label->lab_bbox.r_ytop >>= 3;
    if (expand) label->lab_bbox.r_ytop++;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBFontChar ---
 *
 *	Return information about a single character of a font
 *
 * Results:
 *	Return 0 on success, -1 on failure.
 *
 * Side effects:
 *	Fills in clist, coffset, and cbbox if any are non-NULL.
 *
 * ----------------------------------------------------------------------------
 */

int
DBFontChar(font, ccode, clist, coffset, cbbox)
    int font;		/* Index of font */
    char ccode;		/* ASCII character code */
    FontChar **clist;	/* Return vector list here */
    Point    **coffset; /* Return position offset here */
    Rect     **cbbox;	/* Return bounding box here */
{

    if (font < 0 || font >= DBNumFonts) return - 1;
    if (DBFontList[font] == NULL) return -1;
    if (ccode < 32) ccode = 127;	 /* out-of-bounds code */

    if (clist)   *clist = DBFontList[font]->mf_vectors[ccode - 32];
    if (coffset) *coffset = &DBFontList[font]->mf_offset[ccode - 32];
    if (cbbox)   *cbbox = &DBFontList[font]->mf_bbox[ccode - 32];

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBNameToFont ---
 *
 *	Given a font name, return the index into DBFontList.
 *
 * Results:
 *	-1 if the name is "default", and -2 if the name is not found.
 *	Otherwise, return the index into the DBFontList.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

int
DBNameToFont(name)
    char *name;
{
    int i;
    for (i = 0; i < DBNumFonts; i++)
	if (!strcasecmp(name, DBFontList[i]->mf_name))
	    return i;
    if (!strcasecmp(name, "default"))
	return -1;
    return -2;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBLoadFont ---
 *
 *	Read information from a Type3 font file, and save each character
 *	as a set of vectors forming closed paths.
 *	This routines assumes a *very* limited syntax for Type3 PostScript
 *	fonts, using only moveto, lineto, curveto, and closepath for each
 *	character.  The font dictionary is assumed to be called CharProcs.
 *	These assumptions are valid for Type3 font output generated by
 *	FontForge.
 *
 * Results:
 *	0 on success, -1 on failure.
 *
 * Side effects:
 *	Fills font data structures.
 *
 * ----------------------------------------------------------------------------
 */

#define NUMBUF 16

int
DBLoadFont(fontfile, scale)
    char *fontfile;
    float scale;
{
    FILE *ff;
    const char * const ascii_names[] = {
	"space", "exclam", "quotedbl", "numbersign", "dollar",
	"percent", "ampersand", "quoteright", "parenleft", "parenright",
	"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero",
	"one", "two", "three", "four", "five", "six", "seven", "eight",
	"nine", "colon", "semicolon", "less", "equal", "greater", "question",
	"at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L",
	"M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
	"Z", "bracketleft", "backslash", "bracketright", "asciicircum",
	"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h",
	"i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
	"v", "w", "x", "y", "z", "braceleft", "bar", "braceright",
	"asciitilde", ".notdef", NULL
    };
    int i, j, x, y, asciiidx, chardef, numidx;
    int numtok[NUMBUF];
    char *token, *psname = NULL;
    MagicFont *newFont = NULL, **newDBFontList;
    FontChar *newChar;
    FontPath *newPath, *pathStart = NULL, *curPath, *lastPath;
    Rect extents = GeoNullRect;

    ff = PaOpen(fontfile, "r", (char *)NULL, ".", SysLibPath, (char **)NULL);
    if (ff == NULL) return -1;

    chardef = 0;
    while ((token = dbGetToken(ff)) != NULL)
    {
	if (token[0] == '/') psname = &token[1];  /* Hopefully this doesn't go away */

	if (chardef == 0)
	{
	    if (!strcmp(token, "/FontBBox"))
	    {
		token = dbGetToken(ff);
		if (*token == '{') token++;
		while (isspace(*token)) token++;
		if (StrIsNumeric(token)) extents.r_xbot = (int)round(atof(token));
		token = dbGetToken(ff);
		if (StrIsNumeric(token)) extents.r_ybot = (int)round(atof(token));
		token = dbGetToken(ff);
		if (StrIsNumeric(token)) extents.r_xtop = (int)round(atof(token));
		token = dbGetToken(ff);
		if (StrIsNumeric(token)) extents.r_ytop = (int)round(atof(token));
	    }
	    else if (!strcmp(token, "CharProcs"))
	    {
		token = dbGetToken(ff);
		if (!strcmp(token, "begin"))
		{
		    chardef = 1;
		    newDBFontList = (MagicFont **)mallocMagic((DBNumFonts + 2)
				* sizeof(MagicFont *));
		    newDBFontList[DBNumFonts + 1] = NULL;
		    for (i = 0; i < DBNumFonts; i++)
			newDBFontList[i] = DBFontList[i];
		    if (DBNumFonts > 0) freeMagic((char *)DBFontList);
		    DBFontList = newDBFontList;

		    newFont = (MagicFont *)mallocMagic(sizeof(MagicFont));
		    newDBFontList[DBNumFonts] = newFont;

		    DBNumFonts++;
		    for (asciiidx = 0; asciiidx < 96; asciiidx++)
			newFont->mf_vectors[asciiidx] = NULL;

		    newFont->mf_extents = extents;

		    /* This is a hack, but all these fonts declare	*/
		    /* their "height" to be the Y-value of the tallest	*/
		    /* character, from which one cannot get any useful	*/
		    /* measure of the height of a standard capital	*/
		    /* letter.  I am using an ad-hoc scale passed as an	*/
		    /* argument to the load command (usually ~0.6).	*/

		    newFont->mf_extents.r_ytop =
				(int)((float)newFont->mf_extents.r_ytop
				* scale);
		}
	    }
	    else if (!strcmp(token, "definefont"))
	    {
		if (newFont && psname)
		{
		    char *dotptr = strrchr(psname, '.');
		    if (dotptr != NULL) *dotptr = '\0';
		    newFont->mf_name = StrDup((char **)NULL, psname);
		}
		break;	/* Presumably this is the end of input */
	    }
	}
	else if (chardef == 1)
	{
	    if (!strcmp(token, "currentdict"))
	    {
		token = dbGetToken(ff);
		if (!strcmp(token, "end"))
		    chardef = 0;
	    }

	    /* Look for a character def start */
	    else if (!strcmp(token, "{"))
	    {
		if (psname != NULL)
		{
		    asciiidx = LookupStructFull(psname, ascii_names, sizeof(const char *));
		    if (asciiidx >= 0)
			chardef = 2;
		}
		numidx = 0;
	    }
	}
	else if (chardef == 2)
	{
	    /* Look for character def end */
	    if (!strcmp(token, "}"))
		chardef = 1;

	    /* Pick up values for setcachedevice */
	    if (StrIsNumeric(token))
	    {
		numtok[numidx] = (int)round(atof(token));
		numidx = (numidx + 1) % NUMBUF;
	    }

	    /* Look for path start */
	    else if (!strcmp(token, "setcachedevice"))
	    {
		newChar = NULL;
		chardef = 3;

		/* Fill in bounding box and offset */
		newFont->mf_offset[asciiidx].p_x = numtok[(numidx + NUMBUF - 6)
			% NUMBUF];
		newFont->mf_offset[asciiidx].p_y = numtok[(numidx + NUMBUF - 5)
			% NUMBUF];
		newFont->mf_bbox[asciiidx].r_xbot = numtok[(numidx + NUMBUF - 4)
			% NUMBUF];
		newFont->mf_bbox[asciiidx].r_ybot = numtok[(numidx + NUMBUF - 3)
			% NUMBUF];
		newFont->mf_bbox[asciiidx].r_xtop = numtok[(numidx + NUMBUF - 2)
			% NUMBUF];
		newFont->mf_bbox[asciiidx].r_ytop = numtok[(numidx + NUMBUF - 1)
			% NUMBUF];
	    }
	}
	else if (chardef == 3)
	{
	    if (StrIsNumeric(token))
	    {
		numtok[numidx] = (int)round(atof(token));
		numidx = (numidx + 1) % NUMBUF;
	    }
	    else if (!strcmp(token, "}"))	/* End def */
		chardef = 1;
	    else if (!strcmp(token, "closepath"))
	    {
		if (newChar == NULL)
		{
		    newChar = (FontChar *)mallocMagic(sizeof(FontChar));
		    newFont->mf_vectors[asciiidx] = newChar;
		}
		else
		{
		    newChar->fc_next = (FontChar *)mallocMagic(sizeof(FontChar));
		    newChar = newChar->fc_next;
		}

		/* translate pathStart */
		for (i = 0, curPath = pathStart; curPath->fp_next != NULL;
			curPath = curPath->fp_next)
		    if (!GEO_SAMEPOINT(curPath->fp_next->fp_point, curPath->fp_point))
			i++;

		newChar->fc_numpoints = i;

		/* If the first and last points are the same, remove the last one */
		if (!GEO_SAMEPOINT(curPath->fp_point, pathStart->fp_point))
		    newChar->fc_numpoints++;

		newChar->fc_points = (Point *)mallocMagic(i * sizeof(Point));
		newChar->fc_next = NULL;
		for (i = 0, curPath = pathStart; i < newChar->fc_numpoints;
			curPath = curPath->fp_next)
		{
		    if (curPath->fp_next != NULL)
			if (GEO_SAMEPOINT(curPath->fp_next->fp_point, curPath->fp_point))
			    continue;

		    newChar->fc_points[i].p_x = curPath->fp_point.p_x;
		    newChar->fc_points[i].p_y = curPath->fp_point.p_y;
		    i++;
		}

		/* Remove the pointlist */
		free_magic1_t mm1 = freeMagic1_init();
		for (newPath = pathStart; newPath != NULL; newPath = newPath->fp_next)
		    freeMagic1(&mm1, newPath);
		freeMagic1_end(&mm1);
		pathStart = NULL;
	    }
	    else
	    {
		j = (!strcmp(token, "curveto")) ? 6 : 2;
		lastPath = curPath;
		for (i = j; i > 0; i -= 2)
		{
		    newPath = (FontPath *)mallocMagic(sizeof(FontPath));
		    newPath->fp_next = NULL;
		    newPath->fp_point.p_x = numtok[(numidx + NUMBUF - i) % NUMBUF];
		    newPath->fp_point.p_y = numtok[(numidx + NUMBUF - i + 1) % NUMBUF];
		    if (pathStart == NULL)
			pathStart = newPath;
		    else
			curPath->fp_next = newPath;

		    curPath = newPath;
		}

		/* "moveto" and "lineto" require no further action.  "curveto"	*/
		/* requires that we convert the bezier curve defined by		*/
		/* the last 4 points to a set of line segments.			*/

		if (j == 6)
		{
		    /* Retain the 3 curve points that will be replaced */
		    FontPath *curvePath = lastPath->fp_next;
		    CalcBezierPoints(lastPath, curvePath);
		}
	    }
	}
    }
    fclose(ff);
    return (newFont == NULL) ? -1 : 0;
}
