/* 
 * tkFont.c --
 *
 *	This file maintains a database of fonts for the Tk toolkit.
 *	It also provides several utility procedures for measuring and
 *	displaying text.
 *
 * Copyright (c) 1990-1994 The Regents of the University of California.
 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tkFont.c,v 1.2 1998/09/14 18:23:10 stanton Exp $
 */

#include "tkInt.h"
#include "tkFont.h"

/*
 * The following structure is used to keep track of all the fonts that
 * exist in the current application.  It must be stored in the
 * TkMainInfo for the application.
 */
 
typedef struct TkFontInfo {
    Tcl_HashTable fontCache;	/* Map a string to an existing Tk_Font.
				 * Keys are CachedFontKey structs, values are
				 * TkFont structs. */
    Tcl_HashTable namedTable;	/* Map a name to a set of attributes for a
				 * font, used when constructing a Tk_Font from
				 * a named font description.  Keys are
				 * Tk_Uids, values are NamedFont structs. */
    TkMainInfo *mainPtr;	/* Application that owns this structure. */
    int updatePending;		
} TkFontInfo;

/*
 * The following structure is used as a key in the fontCache.
 */

typedef struct CachedFontKey {
    Display *display;		/* Display for which font was constructed. */
    Tk_Uid string;		/* String that describes font. */
} CachedFontKey;

/*
 * The following data structure is used to keep track of the font attributes
 * for each named font that has been defined.  The named font is only deleted
 * when the last reference to it goes away.
 */

typedef struct NamedFont {
    int refCount;		/* Number of users of named font. */
    int deletePending;		/* Non-zero if font should be deleted when
				 * last reference goes away. */
    TkFontAttributes fa;	/* Desired attributes for named font. */
} NamedFont;
    
/*
 * The following two structures are used to keep track of string
 * measurement information when using the text layout facilities.
 *
 * A LayoutChunk represents a contiguous range of text that can be measured
 * and displayed by low-level text calls.  In general, chunks will be
 * delimited by newlines and tabs.  Low-level, platform-specific things
 * like kerning and non-integer character widths may occur between the
 * characters in a single chunk, but not between characters in different
 * chunks.
 *
 * A TextLayout is a collection of LayoutChunks.  It can be displayed with
 * respect to any origin.  It is the implementation of the Tk_TextLayout
 * opaque token.
 */

typedef struct LayoutChunk {
    CONST char *start;		/* Pointer to simple string to be displayed.
				 * This is a pointer into the TkTextLayout's
				 * string. */
    int numChars;		/* The number of characters in this chunk. */
    int numDisplayChars;	/* The number of characters to display when
				 * this chunk is displayed.  Can be less than
				 * numChars if extra space characters were
				 * absorbed by the end of the chunk.  This
				 * will be < 0 if this is a chunk that is
				 * holding a tab or newline. */
    int x, y;			/* The origin of the first character in this
				 * chunk with respect to the upper-left hand
				 * corner of the TextLayout. */
    int totalWidth;		/* Width in pixels of this chunk.  Used
				 * when hit testing the invisible spaces at
				 * the end of a chunk. */
    int displayWidth;		/* Width in pixels of the displayable
				 * characters in this chunk.  Can be less than
				 * width if extra space characters were
				 * absorbed by the end of the chunk. */
} LayoutChunk;

typedef struct TextLayout {
    Tk_Font tkfont;		/* The font used when laying out the text. */
    CONST char *string;		/* The string that was layed out. */
    int width;			/* The maximum width of all lines in the
				 * text layout. */
    int numChunks;		/* Number of chunks actually used in
				 * following array. */
    LayoutChunk chunks[1];	/* Array of chunks.  The actual size will
				 * be maxChunks.  THIS FIELD MUST BE THE LAST
				 * IN THE STRUCTURE. */
} TextLayout;

/*
 * The following structures are used as two-way maps between the values for
 * the fields in the TkFontAttributes structure and the strings used in
 * Tcl, when parsing both option-value format and style-list format font
 * name strings.
 */

static TkStateMap weightMap[] = {
    {TK_FW_NORMAL,	"normal"},
    {TK_FW_BOLD,	"bold"},
    {TK_FW_UNKNOWN,	NULL}
};

static TkStateMap slantMap[] = {
    {TK_FS_ROMAN,	"roman"},
    {TK_FS_ITALIC,	"italic"},
    {TK_FS_UNKNOWN,	NULL}
};

static TkStateMap underlineMap[] = {
    {1,			"underline"},
    {0,			NULL}
};

static TkStateMap overstrikeMap[] = {
    {1,			"overstrike"},
    {0,			NULL}
};

/*
 * The following structures are used when parsing XLFD's into a set of
 * TkFontAttributes.
 */

static TkStateMap xlfdWeightMap[] = {
    {TK_FW_NORMAL,	"normal"},
    {TK_FW_NORMAL,	"medium"},
    {TK_FW_NORMAL,	"book"},
    {TK_FW_NORMAL,	"light"},
    {TK_FW_BOLD,	"bold"},
    {TK_FW_BOLD,	"demi"},
    {TK_FW_BOLD,	"demibold"},
    {TK_FW_NORMAL,	NULL}		/* Assume anything else is "normal". */
}; 

static TkStateMap xlfdSlantMap[] = {
    {TK_FS_ROMAN,	"r"},
    {TK_FS_ITALIC,	"i"},
    {TK_FS_OBLIQUE,	"o"},
    {TK_FS_ROMAN,	NULL}		/* Assume anything else is "roman". */
};

static TkStateMap xlfdSetwidthMap[] = {
    {TK_SW_NORMAL,	"normal"},
    {TK_SW_CONDENSE,	"narrow"},
    {TK_SW_CONDENSE,	"semicondensed"},
    {TK_SW_CONDENSE,	"condensed"},
    {TK_SW_UNKNOWN,	NULL}
};

static TkStateMap xlfdCharsetMap[] = {
    {TK_CS_NORMAL,	"iso8859"},
    {TK_CS_SYMBOL,	"adobe"},
    {TK_CS_SYMBOL,	"sun"},
    {TK_CS_OTHER,	NULL}
};
    
/*
 * The following structure and defines specify the valid builtin options 
 * when configuring a set of font attributes.
 */

static char *fontOpt[] = {
    "-family",
    "-size",
    "-weight",
    "-slant",
    "-underline",
    "-overstrike",
    NULL
};

#define FONT_FAMILY	0
#define FONT_SIZE	1
#define FONT_WEIGHT	2
#define FONT_SLANT	3
#define FONT_UNDERLINE	4
#define FONT_OVERSTRIKE	5
#define FONT_NUMFIELDS	6	    /* Length of fontOpt array. */

#define GetFontAttributes(tkfont) \
		((CONST TkFontAttributes *) &((TkFont *) (tkfont))->fa)

#define GetFontMetrics(tkfont)    \
		((CONST TkFontMetrics *) &((TkFont *) (tkfont))->fm)


static int		ConfigAttributesObj _ANSI_ARGS_((Tcl_Interp *interp,
			    Tk_Window tkwin, int objc, Tcl_Obj *CONST objv[],
			    TkFontAttributes *faPtr));
static int		FieldSpecified _ANSI_ARGS_((CONST char *field));
static int		GetAttributeInfoObj _ANSI_ARGS_((Tcl_Interp *interp,
			    CONST TkFontAttributes *faPtr, Tcl_Obj *objPtr));
static LayoutChunk *	NewChunk _ANSI_ARGS_((TextLayout **layoutPtrPtr,
			    int *maxPtr, CONST char *start, int numChars,
			    int curX, int newX, int y));
static int		ParseFontNameObj _ANSI_ARGS_((Tcl_Interp *interp,
			    Tk_Window tkwin, Tcl_Obj *objPtr,
			    TkFontAttributes *faPtr));
static void		RecomputeWidgets _ANSI_ARGS_((TkWindow *winPtr));
static void		TheWorldHasChanged _ANSI_ARGS_((
			    ClientData clientData));
static void		UpdateDependantFonts _ANSI_ARGS_((TkFontInfo *fiPtr,
			    Tk_Window tkwin, Tcl_HashEntry *namedHashPtr));

			


/*
 *---------------------------------------------------------------------------
 *
 * TkFontPkgInit --
 *
 *	This procedure is called when an application is created.  It
 *	initializes all the structures that are used by the font
 *	package on a per application basis.
 *
 * Results:
 *	Returns a token that must be stored in the TkMainInfo for this
 *	application.
 *
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */
void
TkFontPkgInit(mainPtr)
    TkMainInfo *mainPtr;	/* The application being created. */
{
    TkFontInfo *fiPtr;

    fiPtr = (TkFontInfo *) ckalloc(sizeof(TkFontInfo));
    Tcl_InitHashTable(&fiPtr->fontCache, sizeof(CachedFontKey) / sizeof(int));
    Tcl_InitHashTable(&fiPtr->namedTable, TCL_ONE_WORD_KEYS);
    fiPtr->mainPtr = mainPtr;
    fiPtr->updatePending = 0;
    mainPtr->fontInfoPtr = fiPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * TkFontPkgFree --
 *
 *	This procedure is called when an application is deleted.  It
 *	deletes all the structures that were used by the font package
 *	for this application.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory freed.
 *
 *---------------------------------------------------------------------------
 */

void
TkFontPkgFree(mainPtr)
    TkMainInfo *mainPtr;	/* The application being deleted. */
{
    TkFontInfo *fiPtr;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;

    fiPtr = mainPtr->fontInfoPtr;

    if (fiPtr->fontCache.numEntries != 0) {
	panic("TkFontPkgFree: all fonts should have been freed already");
    }
    Tcl_DeleteHashTable(&fiPtr->fontCache);

    hPtr = Tcl_FirstHashEntry(&fiPtr->namedTable, &search);
    while (hPtr != NULL) {
	ckfree((char *) Tcl_GetHashValue(hPtr));
	hPtr = Tcl_NextHashEntry(&search);
    }
    Tcl_DeleteHashTable(&fiPtr->namedTable);
    if (fiPtr->updatePending != 0) {
	Tcl_CancelIdleCall(TheWorldHasChanged, (ClientData) fiPtr);
    }
    ckfree((char *) fiPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_FontObjCmd -- 
 *
 *	This procedure is implemented to process the "font" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
Tk_FontObjCmd(clientData, interp, objc, objv)
    ClientData clientData;	/* Main window associated with interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int objc;			/* Number of arguments. */
    Tcl_Obj *CONST objv[];	/* Argument objects. */
{
    int index;
    Tk_Window tkwin;
    TkFontInfo *fiPtr;
    static char *optionStrings[] = {
	"actual",	"configure",	"create",	"delete",
	"families",	"measure",	"metrics",	"names",
	NULL
    };
    enum options {
	FONT_ACTUAL,	FONT_CONFIGURE,	FONT_CREATE,	FONT_DELETE,
	FONT_FAMILIES,	FONT_MEASURE,	FONT_METRICS,	FONT_NAMES
    };

    tkwin = (Tk_Window) clientData;
    fiPtr = ((TkWindow *) tkwin)->mainPtr->fontInfoPtr;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg?");
	return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0,
	    &index) != TCL_OK) {
	return TCL_ERROR;
    }

    switch ((enum options) index) {
	case FONT_ACTUAL: {
	    int skip, result;
	    Tk_Font tkfont;
	    Tcl_Obj *objPtr;
	    CONST TkFontAttributes *faPtr;

	    skip = TkGetDisplayOf(interp, objc - 3, objv + 3, &tkwin);
	    if (skip < 0) {
		return TCL_ERROR;
	    }
	    if ((objc < 3) || (objc - skip > 4)) {
		Tcl_WrongNumArgs(interp, 2, objv,
			"font ?-displayof window? ?option?");
		return TCL_ERROR;
	    }
	    tkfont = Tk_GetFontFromObj(interp, tkwin, objv[2]);
	    if (tkfont == NULL) {
		return TCL_ERROR;
	    }
	    objc -= skip;
	    objv += skip;
	    faPtr = GetFontAttributes(tkfont);
	    objPtr = NULL;
	    if (objc > 3) {
		objPtr = objv[3];
	    }
	    result = GetAttributeInfoObj(interp, faPtr, objPtr);
	    Tk_FreeFont(tkfont);
	    return result;
	}
	case FONT_CONFIGURE: {
	    int result;
	    char *string;
	    Tcl_Obj *objPtr;
	    NamedFont *nfPtr;
	    Tcl_HashEntry *namedHashPtr;

	    if (objc < 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "fontname ?options?");
		return TCL_ERROR;
	    }
	    string = Tk_GetUid(Tcl_GetStringFromObj(objv[2], NULL));
	    namedHashPtr = Tcl_FindHashEntry(&fiPtr->namedTable, string);
	    nfPtr = NULL;		/* lint. */
	    if (namedHashPtr != NULL) {
		nfPtr = (NamedFont *) Tcl_GetHashValue(namedHashPtr);
	    }
	    if ((namedHashPtr == NULL) || (nfPtr->deletePending != 0)) {
		Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "named font \"", string,
			"\" doesn't exist", NULL);
		return TCL_ERROR;
	    }
	    if (objc == 3) {
		objPtr = NULL;
	    } else if (objc == 4) {
		objPtr = objv[3];
	    } else {
		result = ConfigAttributesObj(interp, tkwin, objc - 3,
			objv + 3, &nfPtr->fa);
		UpdateDependantFonts(fiPtr, tkwin, namedHashPtr);
		return result;
	    }
	    return GetAttributeInfoObj(interp, &nfPtr->fa, objPtr);
	}
	case FONT_CREATE: {
	    int skip, i;
	    char *name;
	    char buf[32];
	    TkFontAttributes fa;
	    Tcl_HashEntry *namedHashPtr;

	    skip = 3;
	    if (objc < 3) {
		name = NULL;
	    } else {
		name = Tcl_GetStringFromObj(objv[2], NULL);
		if (name[0] == '-') {
		    name = NULL;
		}
	    }
	    if (name == NULL) {
		/*
		 * No font name specified.  Generate one of the form "fontX".
		 */

		for (i = 1; ; i++) {
		    sprintf(buf, "font%d", i);
		    namedHashPtr = Tcl_FindHashEntry(&fiPtr->namedTable,
			    Tk_GetUid(buf));
		    if (namedHashPtr == NULL) {
			break;
		    }
		}
		name = buf;
		skip = 2;
	    }
	    TkInitFontAttributes(&fa);
	    if (ConfigAttributesObj(interp, tkwin, objc - skip, objv + skip,
		    &fa) != TCL_OK) {
		return TCL_ERROR;
	    }
	    if (TkCreateNamedFont(interp, tkwin, name, &fa) != TCL_OK) {
		return TCL_ERROR;
	    }
	    Tcl_SetStringObj(Tcl_GetObjResult(interp), name, -1);
	    break;
	}
	case FONT_DELETE: {
	    int i;
	    char *string;
	    NamedFont *nfPtr;
	    Tcl_HashEntry *namedHashPtr;

	    /*
	     * Delete the named font.  If there are still widgets using this
	     * font, then it isn't deleted right away.
	     */

	    if (objc < 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "fontname ?fontname ...?");
		return TCL_ERROR;
	    }
	    for (i = 2; i < objc; i++) {
		string = Tk_GetUid(Tcl_GetStringFromObj(objv[i], NULL));
		namedHashPtr = Tcl_FindHashEntry(&fiPtr->namedTable, string);
		if (namedHashPtr == NULL) {
		    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "named font \"", string,
			    "\" doesn't exist", (char *) NULL);
		    return TCL_ERROR;
		}
		nfPtr = (NamedFont *) Tcl_GetHashValue(namedHashPtr);
		if (nfPtr->refCount != 0) {
		    nfPtr->deletePending = 1;
		} else {
		    Tcl_DeleteHashEntry(namedHashPtr);
		    ckfree((char *) nfPtr);
		}
	    }
	    break;
	}
	case FONT_FAMILIES: {
	    int skip;

	    skip = TkGetDisplayOf(interp, objc - 2, objv + 2, &tkwin);
	    if (skip < 0) {
		return TCL_ERROR;
	    }
	    if (objc - skip != 2) {
		Tcl_WrongNumArgs(interp, 2, objv, "?-displayof window?");
		return TCL_ERROR;
	    }
	    TkpGetFontFamilies(interp, tkwin);
	    break;
	}
	case FONT_MEASURE: {
	    char *string;
	    Tk_Font tkfont;
	    int length, skip;
	    
	    skip = TkGetDisplayOf(interp, objc - 3, objv + 3, &tkwin);
	    if (skip < 0) {
		return TCL_ERROR;
	    }
	    if (objc - skip != 4) {
		Tcl_WrongNumArgs(interp, 2, objv,
			"font ?-displayof window? text");
		return TCL_ERROR;
	    }
	    tkfont = Tk_GetFontFromObj(interp, tkwin, objv[2]);
	    if (tkfont == NULL) {
		return TCL_ERROR;
	    }
	    string = Tcl_GetStringFromObj(objv[3 + skip], &length);
	    Tcl_SetIntObj(Tcl_GetObjResult(interp), Tk_TextWidth(tkfont, string, length));
	    Tk_FreeFont(tkfont);
	    break;
	}
	case FONT_METRICS: {
	    char buf[64];
	    Tk_Font tkfont;
	    int skip, index, i;
	    CONST TkFontMetrics *fmPtr;
	    static char *switches[] = {
		"-ascent", "-descent", "-linespace", "-fixed", NULL
	    };

	    skip = TkGetDisplayOf(interp, objc - 3, objv + 3, &tkwin);
	    if (skip < 0) {
		return TCL_ERROR;
	    }
	    if ((objc < 3) || ((objc - skip) > 4)) {
		Tcl_WrongNumArgs(interp, 2, objv,
			"font ?-displayof window? ?option?");
		return TCL_ERROR;
	    }
	    tkfont = Tk_GetFontFromObj(interp, tkwin, objv[2]);
	    if (tkfont == NULL) {
		return TCL_ERROR;
	    }
	    objc -= skip;
	    objv += skip;
	    fmPtr = GetFontMetrics(tkfont);
	    if (objc == 3) {
		sprintf(buf, "-ascent %d -descent %d -linespace %d -fixed %d",
			fmPtr->ascent, fmPtr->descent,
			fmPtr->ascent + fmPtr->descent,
			fmPtr->fixed);
		Tcl_SetStringObj(Tcl_GetObjResult(interp), buf, -1);
	    } else {
		if (Tcl_GetIndexFromObj(interp, objv[3], switches,
			"metric", 0, &index) != TCL_OK) {
		    Tk_FreeFont(tkfont);
		    return TCL_ERROR;
		}
		i = 0;			/* Needed only to prevent compiler
					 * warning. */
		switch (index) {
		    case 0: i = fmPtr->ascent;			break;
		    case 1: i = fmPtr->descent;			break;
		    case 2: i = fmPtr->ascent + fmPtr->descent;	break;
		    case 3: i = fmPtr->fixed;			break;
		}
		Tcl_SetIntObj(Tcl_GetObjResult(interp), i);
	    }
	    Tk_FreeFont(tkfont);
	    break;
	}
	case FONT_NAMES: {
	    char *string;
	    Tcl_Obj *strPtr;
	    NamedFont *nfPtr;
	    Tcl_HashSearch search;
	    Tcl_HashEntry *namedHashPtr;
	    
	    if (objc != 2) {
		Tcl_WrongNumArgs(interp, 1, objv, "names");
		return TCL_ERROR;
	    }
	    namedHashPtr = Tcl_FirstHashEntry(&fiPtr->namedTable, &search);
	    while (namedHashPtr != NULL) {
		nfPtr = (NamedFont *) Tcl_GetHashValue(namedHashPtr);
		if (nfPtr->deletePending == 0) {
		    string = Tcl_GetHashKey(&fiPtr->namedTable, namedHashPtr);
		    strPtr = Tcl_NewStringObj(string, -1);
		    Tcl_ListObjAppendElement(NULL, Tcl_GetObjResult(interp), strPtr);
		}
		namedHashPtr = Tcl_NextHashEntry(&search);
	    }
	    break;
	}
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * UpdateDependantFonts, TheWorldHasChanged, RecomputeWidgets --
 *
 *	Called when the attributes of a named font changes.  Updates all
 *	the instantiated fonts that depend on that named font and then
 *	uses the brute force approach and prepares every widget to
 *	recompute its geometry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Things get queued for redisplay.
 *
 *---------------------------------------------------------------------------
 */

static void
UpdateDependantFonts(fiPtr, tkwin, namedHashPtr)
    TkFontInfo *fiPtr;		/* Info about application's fonts. */
    Tk_Window tkwin;		/* A window in the application. */
    Tcl_HashEntry *namedHashPtr;/* The named font that is changing. */
{
    Tcl_HashEntry *cacheHashPtr;
    Tcl_HashSearch search;
    TkFont *fontPtr;
    NamedFont *nfPtr;

    nfPtr = (NamedFont *) Tcl_GetHashValue(namedHashPtr);
    if (nfPtr->refCount == 0) {
	/*
	 * Well nobody's using this named font, so don't have to tell
	 * any widgets to recompute themselves.
	 */

	return;
    }


    cacheHashPtr = Tcl_FirstHashEntry(&fiPtr->fontCache, &search);
    while (cacheHashPtr != NULL) {
	fontPtr = (TkFont *) Tcl_GetHashValue(cacheHashPtr);
	if (fontPtr->namedHashPtr == namedHashPtr) {
	    TkpGetFontFromAttributes(fontPtr, tkwin, &nfPtr->fa);
	    if (fiPtr->updatePending == 0) {
		fiPtr->updatePending = 1;
		Tcl_DoWhenIdle(TheWorldHasChanged, (ClientData) fiPtr);
	    }
	}
	cacheHashPtr = Tcl_NextHashEntry(&search);
    }
}

static void
TheWorldHasChanged(clientData)
    ClientData clientData;	/* Info about application's fonts. */
{
    TkFontInfo *fiPtr;

    fiPtr = (TkFontInfo *) clientData;
    fiPtr->updatePending = 0;

    RecomputeWidgets(fiPtr->mainPtr->winPtr);
}

static void
RecomputeWidgets(winPtr)
    TkWindow *winPtr;		/* Window to which command is sent. */
{
    if ((winPtr->classProcsPtr != NULL)
	    && (winPtr->classProcsPtr->geometryProc != NULL)) {
	(*winPtr->classProcsPtr->geometryProc)(winPtr->instanceData);
    }
    for (winPtr = winPtr->childList; winPtr != NULL; winPtr = winPtr->nextPtr) {
	RecomputeWidgets(winPtr);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * TkCreateNamedFont --
 *
 *	Create the specified named font with the given attributes in the
 *	named font table associated with the interp.  
 *
 * Results:
 *	Returns TCL_OK if the font was successfully created, or TCL_ERROR
 *	if the named font already existed.  If TCL_ERROR is returned, an
 *	error message is left in interp->result.
 *
 * Side effects:
 *	Assume there used to exist a named font by the specified name, and
 *	that the named font had been deleted, but there were still some
 *	widgets using the named font at the time it was deleted.  If a
 *	new named font is created with the same name, all those widgets
 *	that were using the old named font will be redisplayed using
 *	the new named font's attributes.
 *
 *---------------------------------------------------------------------------
 */

int
TkCreateNamedFont(interp, tkwin, name, faPtr)
    Tcl_Interp *interp;		/* Interp for error return. */
    Tk_Window tkwin;		/* A window associated with interp. */
    CONST char *name;		/* Name for the new named font. */
    TkFontAttributes *faPtr;	/* Attributes for the new named font. */
{
    TkFontInfo *fiPtr;
    Tcl_HashEntry *namedHashPtr;
    int new;
    NamedFont *nfPtr;    

    fiPtr = ((TkWindow *) tkwin)->mainPtr->fontInfoPtr;

    name = Tk_GetUid(name);
    namedHashPtr = Tcl_CreateHashEntry(&fiPtr->namedTable, name, &new);
		    
    if (new == 0) {
	nfPtr = (NamedFont *) Tcl_GetHashValue(namedHashPtr);
	if (nfPtr->deletePending == 0) {
	    interp->result[0] = '\0';
	    Tcl_AppendResult(interp, "font \"", name,
		    "\" already exists", (char *) NULL);
	    return TCL_ERROR;
	}

	/*
	 * Recreating a named font with the same name as a previous
	 * named font.  Some widgets were still using that named
	 * font, so they need to get redisplayed.
	 */

	nfPtr->fa = *faPtr;
	nfPtr->deletePending = 0;
	UpdateDependantFonts(fiPtr, tkwin, namedHashPtr);
	return TCL_OK;
    }

    nfPtr = (NamedFont *) ckalloc(sizeof(NamedFont));
    nfPtr->deletePending = 0;
    Tcl_SetHashValue(namedHashPtr, nfPtr);
    nfPtr->fa = *faPtr;
    nfPtr->refCount = 0;	
    nfPtr->deletePending = 0;
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetFont -- 
 *
 *	Given a string description of a font, map the description to a
 *	corresponding Tk_Font that represents the font.
 *
 * Results:
 *	The return value is token for the font, or NULL if an error
 *	prevented the font from being created.  If NULL is returned, an
 *	error message will be left in interp->result.
 *
 * Side effects:
 *	Calls Tk_GetFontFromObj(), which modifies interp's result object,
 *	then copies the string from the result object into interp->result.
 *	This procedure will go away when Tk_ConfigureWidget() is
 *	made into an object command.
 *
 *---------------------------------------------------------------------------
 */

Tk_Font
Tk_GetFont(interp, tkwin, string)
    Tcl_Interp *interp;		/* Interp for database and error return. */
    Tk_Window tkwin;		/* For display on which font will be used. */
    CONST char *string;		/* String describing font, as: named font,
				 * native format, or parseable string. */
{
    Tcl_Obj *strPtr;
    Tk_Font tkfont;
    
    strPtr = Tcl_NewStringObj((char *) string, -1);
    
    tkfont = Tk_GetFontFromObj(interp, tkwin, strPtr);
    if (tkfont == NULL) {
	Tcl_SetResult(interp,
	        Tcl_GetStringFromObj(Tcl_GetObjResult(interp), NULL),
		TCL_VOLATILE);
    }

    Tcl_DecrRefCount(strPtr);	/* done with object */
    return tkfont;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetFontFromObj -- 
 *
 *	Given a string description of a font, map the description to a
 *	corresponding Tk_Font that represents the font.
 *
 * Results:
 *	The return value is token for the font, or NULL if an error
 *	prevented the font from being created.  If NULL is returned, an
 *	error message will be left in interp's result object.
 *
 * Side effects:
 * 	The font is added to an internal database with a reference
 *	count.  For each call to this procedure, there should eventually
 *	be a call to Tk_FreeFont() so that the database is cleaned up when
 *	fonts aren't in use anymore.
 *
 *---------------------------------------------------------------------------
 */

Tk_Font
Tk_GetFontFromObj(interp, tkwin, objPtr)
    Tcl_Interp *interp;		/* Interp for database and error return. */
    Tk_Window tkwin;		/* For display on which font will be used. */
    Tcl_Obj *objPtr;		/* Object describing font, as: named font,
				 * native format, or parseable string. */
{
    TkFontInfo *fiPtr;
    CachedFontKey key;
    Tcl_HashEntry *cacheHashPtr, *namedHashPtr;
    TkFont *fontPtr;
    int new, descent;
    NamedFont *nfPtr;
    char *string;
    
    fiPtr = ((TkWindow *) tkwin)->mainPtr->fontInfoPtr;
    string = Tcl_GetStringFromObj(objPtr, NULL);

    key.display = Tk_Display(tkwin);
    key.string = Tk_GetUid(string);
    cacheHashPtr = Tcl_CreateHashEntry(&fiPtr->fontCache, (char *) &key, &new);

    if (new == 0) {
	/*
	 * We have already constructed a font with this description for
	 * this display.  Bump the reference count of the cached font.
	 */

	fontPtr = (TkFont *) Tcl_GetHashValue(cacheHashPtr);
	fontPtr->refCount++;
	return (Tk_Font) fontPtr;
    }

    namedHashPtr = Tcl_FindHashEntry(&fiPtr->namedTable, key.string);
    if (namedHashPtr != NULL) {
	/*
	 * Construct a font based on a named font.
	 */

	nfPtr = (NamedFont *) Tcl_GetHashValue(namedHashPtr);
	nfPtr->refCount++;

	fontPtr = TkpGetFontFromAttributes(NULL, tkwin, &nfPtr->fa);
    } else {
	/*
	 * Native font?
	 */

	fontPtr = TkpGetNativeFont(tkwin, string);
	if (fontPtr == NULL) {
	    TkFontAttributes fa;

	    TkInitFontAttributes(&fa);
	    if (ParseFontNameObj(interp, tkwin, objPtr, &fa) != TCL_OK) {
		Tcl_DeleteHashEntry(cacheHashPtr);
		return NULL;
	    }

	    /*
	     * String contained the attributes inline.
	     */

	    fontPtr = TkpGetFontFromAttributes(NULL, tkwin, &fa);
	}
    }
    Tcl_SetHashValue(cacheHashPtr, fontPtr);

    fontPtr->refCount	    = 1;
    fontPtr->cacheHashPtr   = cacheHashPtr;
    fontPtr->namedHashPtr   = namedHashPtr;

    Tk_MeasureChars((Tk_Font) fontPtr, "0", 1, 0, 0, &fontPtr->tabWidth);
    if (fontPtr->tabWidth == 0) {
	fontPtr->tabWidth = fontPtr->fm.maxWidth;
    }
    fontPtr->tabWidth *= 8;

    /*
     * Make sure the tab width isn't zero (some fonts may not have enough
     * information to set a reasonable tab width).
     */

    if (fontPtr->tabWidth == 0) {
	fontPtr->tabWidth = 1;
    }

    /*
     * Get information used for drawing underlines in generic code on a
     * non-underlined font.
     */
    
    descent = fontPtr->fm.descent;
    fontPtr->underlinePos = descent / 2;
    fontPtr->underlineHeight = fontPtr->fa.pointsize / 10;
    if (fontPtr->underlineHeight == 0) {
	fontPtr->underlineHeight = 1;
    }
    if (fontPtr->underlinePos + fontPtr->underlineHeight > descent) {
	/*
	 * If this set of values would cause the bottom of the underline
	 * bar to stick below the descent of the font, jack the underline
	 * up a bit higher.
	 */

	fontPtr->underlineHeight = descent - fontPtr->underlinePos;
	if (fontPtr->underlineHeight == 0) {
	    fontPtr->underlinePos--;
	    fontPtr->underlineHeight = 1;
	}
    }
    
    return (Tk_Font) fontPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_NameOfFont --
 *
 *	Given a font, return a textual string identifying it.
 *
 * Results:
 *	The return value is the description that was passed to
 *	Tk_GetFont() to create the font.  The storage for the returned
 *	string is only guaranteed to persist until the font is deleted.
 *	The caller should not modify this string.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

char *
Tk_NameOfFont(tkfont)
    Tk_Font tkfont;		/* Font whose name is desired. */
{
    TkFont *fontPtr;
    Tcl_HashEntry *hPtr;
    CachedFontKey *keyPtr;

    fontPtr = (TkFont *) tkfont;
    hPtr = fontPtr->cacheHashPtr;

    keyPtr = (CachedFontKey *) Tcl_GetHashKey(hPtr->tablePtr, hPtr);
    return (char *) keyPtr->string;    
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_FreeFont -- 
 *
 *	Called to release a font allocated by Tk_GetFont().
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The reference count associated with font is decremented, and
 *	only deallocated when no one is using it.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_FreeFont(tkfont)
    Tk_Font tkfont;		/* Font to be released. */
{
    TkFont *fontPtr;
    NamedFont *nfPtr;

    if (tkfont == NULL) {
	return;
    }
    fontPtr = (TkFont *) tkfont;
    fontPtr->refCount--;
    if (fontPtr->refCount == 0) {
	if (fontPtr->namedHashPtr != NULL) {
	    /*
	     * The font is being deleted.  Determine if the associated named
	     * font definition should and/or can be deleted too.
	     */

	    nfPtr = (NamedFont *) Tcl_GetHashValue(fontPtr->namedHashPtr);
	    nfPtr->refCount--;
	    if ((nfPtr->refCount == 0) && (nfPtr->deletePending != 0)) {
		Tcl_DeleteHashEntry(fontPtr->namedHashPtr);
		ckfree((char *) nfPtr);
	    }
	}
	Tcl_DeleteHashEntry(fontPtr->cacheHashPtr);
	TkpDeleteFont(fontPtr);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_FontId --
 *
 *	Given a font, return an opaque handle that should be selected
 *	into the XGCValues structure in order to get the constructed
 *	gc to use this font.  This procedure would go away if the
 *	XGCValues structure were replaced with a TkGCValues structure.
 *
 * Results:
 *	As above.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

Font
Tk_FontId(tkfont)
    Tk_Font tkfont;	/* Font that is going to be selected into GC. */
{
    TkFont *fontPtr;

    fontPtr = (TkFont *) tkfont;
    return fontPtr->fid;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetFontMetrics --
 *
 *	Returns overall ascent and descent metrics for the given font.
 *	These values can be used to space multiple lines of text and
 *	to align the baselines of text in different fonts.
 *
 * Results:
 *	If *heightPtr is non-NULL, it is filled with the overall height
 *	of the font, which is the sum of the ascent and descent.
 *	If *ascentPtr or *descentPtr is non-NULL, they are filled with
 *	the ascent and/or descent information for the font.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */
void
Tk_GetFontMetrics(tkfont, fmPtr)
    Tk_Font tkfont;		/* Font in which metrics are calculated. */
    Tk_FontMetrics *fmPtr;	/* Pointer to structure in which font
				 * metrics for tkfont will be stored. */
{
    TkFont *fontPtr;

    fontPtr = (TkFont *) tkfont;
    fmPtr->ascent = fontPtr->fm.ascent;
    fmPtr->descent = fontPtr->fm.descent;
    fmPtr->linespace = fontPtr->fm.ascent + fontPtr->fm.descent;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_PostscriptFontName --
 *
 *	Given a Tk_Font, return the name of the corresponding Postscript
 *	font.
 *
 * Results:
 *	The return value is the pointsize of the given Tk_Font.
 *	The name of the Postscript font is appended to dsPtr.
 *
 * Side effects:
 *	If the font does not exist on the printer, the print job will
 *	fail at print time.  Given a "reasonable" Postscript printer,
 *	the following Tk_Font font families should print correctly:
 *
 *	    Avant Garde, Arial, Bookman, Courier, Courier New, Geneva,
 *	    Helvetica, Monaco, New Century Schoolbook, New York,
 *	    Palatino, Symbol, Times, Times New Roman, Zapf Chancery,
 *	    and Zapf Dingbats.
 *
 *	Any other Tk_Font font families may not print correctly
 *	because the computed Postscript font name may be incorrect.
 *
 *---------------------------------------------------------------------------
 */


int
Tk_PostscriptFontName(tkfont, dsPtr)
    Tk_Font tkfont;		/* Font in which text will be printed. */
    Tcl_DString *dsPtr;		/* Pointer to an initialized Tcl_DString to
				 * which the name of the Postscript font that
				 * corresponds to tkfont will be appended. */
{
    TkFont *fontPtr;
    char *family, *weightString, *slantString;
    char *src, *dest;
    int upper, len;

    len = Tcl_DStringLength(dsPtr);
    fontPtr = (TkFont *) tkfont;

    /*
     * Convert the case-insensitive Tk_Font family name to the
     * case-sensitive Postscript family name.  Take out any spaces and
     * capitalize the first letter of each word.
     */

    family = fontPtr->fa.family;
    if (strncasecmp(family, "itc ", 4) == 0) {
	family = family + 4;
    }
    if ((strcasecmp(family, "Arial") == 0)
	    || (strcasecmp(family, "Geneva") == 0)) {
	family = "Helvetica";
    } else if ((strcasecmp(family, "Times New Roman") == 0)
	    || (strcasecmp(family, "New York") == 0)) {
	family = "Times";
    } else if ((strcasecmp(family, "Courier New") == 0)
	    || (strcasecmp(family, "Monaco") == 0)) {
	family = "Courier";
    } else if (strcasecmp(family, "AvantGarde") == 0) {
	family = "AvantGarde";
    } else if (strcasecmp(family, "ZapfChancery") == 0) {
	family = "ZapfChancery";
    } else if (strcasecmp(family, "ZapfDingbats") == 0) {
	family = "ZapfDingbats";
    } else {
	/*
	 * Inline, capitalize the first letter of each word, lowercase the
	 * rest of the letters in each word, and then take out the spaces
	 * between the words.  This may make the DString shorter, which is
	 * safe to do.
	 */

	Tcl_DStringAppend(dsPtr, family, -1);

	src = dest = Tcl_DStringValue(dsPtr) + len;
	upper = 1;
	for (; *src != '\0'; src++, dest++) {
	    while (isspace(UCHAR(*src))) {
		src++;
		upper = 1;
	    }
	    *dest = *src;
	    if ((upper != 0) && (islower(UCHAR(*src)))) {
		*dest = toupper(UCHAR(*src));
	    }
	    upper = 0;
	}
	*dest = '\0';
	Tcl_DStringSetLength(dsPtr, dest - Tcl_DStringValue(dsPtr));
	family = Tcl_DStringValue(dsPtr) + len;
    }
    if (family != Tcl_DStringValue(dsPtr) + len) {
	Tcl_DStringAppend(dsPtr, family, -1);
	family = Tcl_DStringValue(dsPtr) + len;
    }

    if (strcasecmp(family, "NewCenturySchoolbook") == 0) {
	Tcl_DStringSetLength(dsPtr, len);
	Tcl_DStringAppend(dsPtr, "NewCenturySchlbk", -1);
	family = Tcl_DStringValue(dsPtr) + len;
    }

    /*
     * Get the string to use for the weight.
     */

    weightString = NULL;
    if (fontPtr->fa.weight == TK_FW_NORMAL) {
	if (strcmp(family, "Bookman") == 0) {
	    weightString = "Light";
	} else if (strcmp(family, "AvantGarde") == 0) {
	    weightString = "Book";
	} else if (strcmp(family, "ZapfChancery") == 0) {
	    weightString = "Medium";
	}
    } else {
	if ((strcmp(family, "Bookman") == 0)
		|| (strcmp(family, "AvantGarde") == 0)) {
	    weightString = "Demi";
	} else {
	    weightString = "Bold";
	}
    }

    /*
     * Get the string to use for the slant.
     */

    slantString = NULL;
    if (fontPtr->fa.slant == TK_FS_ROMAN) {
	;
    } else {
	if ((strcmp(family, "Helvetica") == 0)
		|| (strcmp(family, "Courier") == 0)
		|| (strcmp(family, "AvantGarde") == 0)) {
	    slantString = "Oblique";
	} else {
	    slantString = "Italic";
	}
    }

    /*
     * The string "Roman" needs to be added to some fonts that are not bold
     * and not italic.
     */

    if ((slantString == NULL) && (weightString == NULL)) {
	if ((strcmp(family, "Times") == 0) 
		|| (strcmp(family, "NewCenturySchlbk") == 0)
		|| (strcmp(family, "Palatino") == 0)) {
	    Tcl_DStringAppend(dsPtr, "-Roman", -1);
	}
    } else {
	Tcl_DStringAppend(dsPtr, "-", -1);
	if (weightString != NULL) {
	    Tcl_DStringAppend(dsPtr, weightString, -1);
	}
	if (slantString != NULL) {
	    Tcl_DStringAppend(dsPtr, slantString, -1);
	}
    }

    return fontPtr->fa.pointsize;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_TextWidth --
 *
 *	A wrapper function for the more complicated interface of
 *	Tk_MeasureChars.  Computes how much space the given
 *	simple string needs.
 *
 * Results:
 *	The return value is the width (in pixels) of the given string.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

int
Tk_TextWidth(tkfont, string, numChars)
    Tk_Font tkfont;		/* Font in which text will be measured. */
    CONST char *string;		/* String whose width will be computed. */
    int numChars;		/* Number of characters to consider from
				 * string, or < 0 for strlen(). */
{
    int width;

    if (numChars < 0) {
	numChars = strlen(string);
    }
    Tk_MeasureChars(tkfont, string, numChars, 0, 0, &width);
    return width;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_UnderlineChars --
 *
 *	This procedure draws an underline for a given range of characters
 *	in a given string.  It doesn't draw the characters (which are
 *	assumed to have been displayed previously); it just draws the
 *	underline.  This procedure would mainly be used to quickly
 *	underline a few characters without having to construct an
 *	underlined font.  To produce properly underlined text, the
 *	appropriate underlined font should be constructed and used. 
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information gets displayed in "drawable".
 *
 *----------------------------------------------------------------------
 */

void
Tk_UnderlineChars(display, drawable, gc, tkfont, string, x, y, firstChar,
	lastChar)
    Display *display;		/* Display on which to draw. */
    Drawable drawable;		/* Window or pixmap in which to draw. */
    GC gc;			/* Graphics context for actually drawing
				 * line. */
    Tk_Font tkfont;		/* Font used in GC;  must have been allocated
				 * by Tk_GetFont().  Used for character
				 * dimensions, etc. */
    CONST char *string;		/* String containing characters to be
				 * underlined or overstruck. */
    int x, y;			/* Coordinates at which first character of
				 * string is drawn. */
    int firstChar;		/* Index of first character. */
    int lastChar;		/* Index of one after the last character. */
{
    TkFont *fontPtr;
    int startX, endX;

    fontPtr = (TkFont *) tkfont;
    
    Tk_MeasureChars(tkfont, string, firstChar, 0, 0, &startX);
    Tk_MeasureChars(tkfont, string, lastChar, 0, 0, &endX);

    XFillRectangle(display, drawable, gc, x + startX,
	    y + fontPtr->underlinePos, (unsigned int) (endX - startX),
	    (unsigned int) fontPtr->underlineHeight);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_ComputeTextLayout --
 *
 *	Computes the amount of screen space needed to display a
 *	multi-line, justified string of text.  Records all the
 *	measurements that were done to determine to size and
 *	positioning of the individual lines of text; this information
 *	can be used by the Tk_DrawTextLayout() procedure to
 *	display the text quickly (without remeasuring it).
 *
 *	This procedure is useful for simple widgets that want to
 *	display single-font, multi-line text and want Tk to handle the
 *	details.
 *
 * Results:
 *	The return value is a Tk_TextLayout token that holds the
 *	measurement information for the given string.  The token is
 *	only valid for the given string.  If the string is freed,
 *	the token is no longer valid and must also be freed.  To free
 *	the token, call Tk_FreeTextLayout().
 *
 *	The dimensions of the screen area needed to display the text
 *	are stored in *widthPtr and *heightPtr.
 *
 * Side effects:
 *	Memory is allocated to hold the measurement information.  
 *
 *---------------------------------------------------------------------------
 */

Tk_TextLayout
Tk_ComputeTextLayout(tkfont, string, numChars, wrapLength, justify, flags,
	widthPtr, heightPtr)
    Tk_Font tkfont;		/* Font that will be used to display text. */
    CONST char *string;		/* String whose dimensions are to be
				 * computed. */
    int numChars;		/* Number of characters to consider from
				 * string, or < 0 for strlen(). */
    int wrapLength;		/* Longest permissible line length, in
				 * pixels.  <= 0 means no automatic wrapping:
				 * just let lines get as long as needed. */
    Tk_Justify justify;		/* How to justify lines. */
    int flags;			/* Flag bits OR-ed together.
				 * TK_IGNORE_TABS means that tab characters
				 * should not be expanded.  TK_IGNORE_NEWLINES
				 * means that newline characters should not
				 * cause a line break. */
    int *widthPtr;		/* Filled with width of string. */
    int *heightPtr;		/* Filled with height of string. */
{
    TkFont *fontPtr;
    CONST char *start, *end, *special;
    int n, y, charsThisChunk, maxChunks;
    int baseline, height, curX, newX, maxWidth;
    TextLayout *layoutPtr;
    LayoutChunk *chunkPtr;
    CONST TkFontMetrics *fmPtr;
#define MAX_LINES 50
    int staticLineLengths[MAX_LINES];
    int *lineLengths;
    int maxLines, curLine, layoutHeight;

    lineLengths = staticLineLengths;
    maxLines = MAX_LINES;
    
    fontPtr = (TkFont *) tkfont;
    fmPtr = &fontPtr->fm;

    height = fmPtr->ascent + fmPtr->descent;

    if (numChars < 0) {
	numChars = strlen(string);
    }

    maxChunks = 1;

    layoutPtr = (TextLayout *) ckalloc(sizeof(TextLayout)
	    + (maxChunks - 1) * sizeof(LayoutChunk));
    layoutPtr->tkfont	    = tkfont;
    layoutPtr->string	    = string;
    layoutPtr->numChunks    = 0;

    baseline = fmPtr->ascent;
    maxWidth = 0;

    /*
     * Divide the string up into simple strings and measure each string.
     */

    curX = 0;

    end = string + numChars;
    special = string;

    flags &= TK_IGNORE_TABS | TK_IGNORE_NEWLINES;
    flags |= TK_WHOLE_WORDS | TK_AT_LEAST_ONE;	    
    curLine = 0;
    for (start = string; start < end; ) {
	if (start >= special) {
	    /*
	     * Find the next special character in the string.
	     */

	    for (special = start; special < end; special++) {
		if (!(flags & TK_IGNORE_NEWLINES)) {
		    if ((*special == '\n') || (*special == '\r')) {
			break;
		    }
		}
		if (!(flags & TK_IGNORE_TABS)) {
		    if (*special == '\t') {
			break;
		    }
		}
	    }
	}

	/*
	 * Special points at the next special character (or the end of the
	 * string).  Process characters between start and special.
	 */

	chunkPtr = NULL;
	if (start < special) {
	    charsThisChunk = Tk_MeasureChars(tkfont, start, special - start,
		    wrapLength - curX, flags, &newX);
	    newX += curX;
	    flags &= ~TK_AT_LEAST_ONE;
	    if (charsThisChunk > 0) {
		chunkPtr = NewChunk(&layoutPtr, &maxChunks, start,
			charsThisChunk, curX, newX, baseline);
			
		start += charsThisChunk;
		curX = newX;
	    }
	}

	if ((start == special) && (special < end)) {
	    /*
	     * Handle the special character.
	     */

	    chunkPtr = NULL;
	    if (*special == '\t') {
		newX = curX + fontPtr->tabWidth;
		newX -= newX % fontPtr->tabWidth;
		NewChunk(&layoutPtr, &maxChunks, start, 1, curX, newX,
			baseline)->numDisplayChars = -1;
		start++;
		if ((start < end) &&
			((wrapLength <= 0) || (newX <= wrapLength))) {
		    /*
		     * More chars can still fit on this line.
		     */

		    curX = newX;
		    flags &= ~TK_AT_LEAST_ONE;
		    continue;
		}
	    } else {	
		NewChunk(&layoutPtr, &maxChunks, start, 1, curX, 1000000000,
			baseline)->numDisplayChars = -1;
		start++;
		goto wrapLine;
	    }
	}

	/*
	 * No more characters are going to go on this line, either because
	 * no more characters can fit or there are no more characters left.
	 * Consume all extra spaces at end of line.  
	 */

	while ((start < end) && isspace(UCHAR(*start))) {
	    if (!(flags & TK_IGNORE_NEWLINES)) {
		if ((*start == '\n') || (*start == '\r')) {
		    break;
		}
	    }
	    if (!(flags & TK_IGNORE_TABS)) {
		if (*start == '\t') {
		    break;
		}
	    }
	    start++;
	}
	if (chunkPtr != NULL) {
	    /*
	     * Append all the extra spaces on this line to the end of the
	     * last text chunk.
	     */
	    charsThisChunk = start - (chunkPtr->start + chunkPtr->numChars);
	    if (charsThisChunk > 0) {
		chunkPtr->numChars += Tk_MeasureChars(tkfont,
			chunkPtr->start + chunkPtr->numChars, charsThisChunk,
			0, 0, &chunkPtr->totalWidth);
		chunkPtr->totalWidth += curX;
	    }
	}

        wrapLine: 
	flags |= TK_AT_LEAST_ONE;

	/*
	 * Save current line length, then move current position to start of
	 * next line.
	 */

	if (curX > maxWidth) {
	    maxWidth = curX;
	}

	/*
	 * Remember width of this line, so that all chunks on this line
	 * can be centered or right justified, if necessary.
	 */

	if (curLine >= maxLines) {
	    int *newLengths;
	    
	    newLengths = (int *) ckalloc(2 * maxLines * sizeof(int));
	    memcpy((void *) newLengths, lineLengths, maxLines * sizeof(int));
	    if (lineLengths != staticLineLengths) {
	        ckfree((char *) lineLengths);
	    }
	    lineLengths = newLengths;
	    maxLines *= 2;
	}
	lineLengths[curLine] = curX;
	curLine++;

	curX = 0;
	baseline += height;
    }

    /*
     * If last line ends with a newline, then we need to make a 0 width
     * chunk on the next line.  Otherwise "Hello" and "Hello\n" are the
     * same height.
     */

    if ((layoutPtr->numChunks > 0) && ((flags & TK_IGNORE_NEWLINES) == 0)) {
	if (layoutPtr->chunks[layoutPtr->numChunks - 1].start[0] == '\n') {
	    chunkPtr = NewChunk(&layoutPtr, &maxChunks, start, 0, curX,
		    1000000000, baseline);
	    chunkPtr->numDisplayChars = -1;
	    baseline += height;
	}
    }	    

    /*
     * Using maximum line length, shift all the chunks so that the lines are
     * all justified correctly.
     */
    
    curLine = 0;
    chunkPtr = layoutPtr->chunks;
    y = chunkPtr->y;
    for (n = 0; n < layoutPtr->numChunks; n++) {
	int extra;

	if (chunkPtr->y != y) {
	    curLine++;
	    y = chunkPtr->y;
	}
	extra = maxWidth - lineLengths[curLine];
	if (justify == TK_JUSTIFY_CENTER) {
	    chunkPtr->x += extra / 2;
	} else if (justify == TK_JUSTIFY_RIGHT) {
	    chunkPtr->x += extra;
	}
	chunkPtr++;
    }

    layoutPtr->width = maxWidth;
    layoutHeight = baseline - fmPtr->ascent;
    if (layoutPtr->numChunks == 0) {
	layoutHeight = height;

	/*
	 * This fake chunk is used by the other procedures so that they can
	 * pretend that there is a chunk with no chars in it, which makes
	 * the coding simpler.
	 */

	layoutPtr->numChunks = 1;
	layoutPtr->chunks[0].start		= string;
	layoutPtr->chunks[0].numChars		= 0;
	layoutPtr->chunks[0].numDisplayChars	= -1;
	layoutPtr->chunks[0].x			= 0;
	layoutPtr->chunks[0].y			= fmPtr->ascent;
	layoutPtr->chunks[0].totalWidth		= 0;
	layoutPtr->chunks[0].displayWidth	= 0;
    }

    if (widthPtr != NULL) {
	*widthPtr = layoutPtr->width;
    }
    if (heightPtr != NULL) {
	*heightPtr = layoutHeight;
    }
    if (lineLengths != staticLineLengths) {
	ckfree((char *) lineLengths);
    }

    return (Tk_TextLayout) layoutPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_FreeTextLayout --
 *
 *	This procedure is called to release the storage associated with
 *	a Tk_TextLayout when it is no longer needed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is freed.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_FreeTextLayout(textLayout)
    Tk_TextLayout textLayout;	/* The text layout to be released. */
{
    TextLayout *layoutPtr;

    layoutPtr = (TextLayout *) textLayout;
    if (layoutPtr != NULL) {
	ckfree((char *) layoutPtr);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_DrawTextLayout --
 *
 *	Use the information in the Tk_TextLayout token to display a
 *	multi-line, justified string of text.
 *
 *	This procedure is useful for simple widgets that need to
 *	display single-font, multi-line text and want Tk to handle
 *	the details.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Text drawn on the screen.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_DrawTextLayout(display, drawable, gc, layout, x, y, firstChar, lastChar)
    Display *display;		/* Display on which to draw. */
    Drawable drawable;		/* Window or pixmap in which to draw. */
    GC gc;			/* Graphics context to use for drawing text. */
    Tk_TextLayout layout;	/* Layout information, from a previous call
				 * to Tk_ComputeTextLayout(). */
    int x, y;			/* Upper-left hand corner of rectangle in
				 * which to draw (pixels). */
    int firstChar;		/* The index of the first character to draw
				 * from the given text item.  0 specfies the
				 * beginning. */
    int lastChar;		/* The index just after the last character
				 * to draw from the given text item.  A number
				 * < 0 means to draw all characters. */
{
    TextLayout *layoutPtr;
    int i, numDisplayChars, drawX;
    LayoutChunk *chunkPtr;

    layoutPtr = (TextLayout *) layout;
    if (layoutPtr == NULL) {
	return;
    }

    if (lastChar < 0) {
	lastChar = 100000000;
    }
    chunkPtr = layoutPtr->chunks;
    for (i = 0; i < layoutPtr->numChunks; i++) {
	numDisplayChars = chunkPtr->numDisplayChars;
	if ((numDisplayChars > 0) && (firstChar < numDisplayChars)) {
	    if (firstChar <= 0) {
		drawX = 0;
		firstChar = 0;
	    } else {
		Tk_MeasureChars(layoutPtr->tkfont, chunkPtr->start, firstChar,
			0, 0, &drawX);
	    }
	    if (lastChar < numDisplayChars) {
		numDisplayChars = lastChar;
	    }
	    Tk_DrawChars(display, drawable, gc, layoutPtr->tkfont,
		    chunkPtr->start + firstChar, numDisplayChars - firstChar,
		    x + chunkPtr->x + drawX, y + chunkPtr->y);
	}
	firstChar -= chunkPtr->numChars;
	lastChar -= chunkPtr->numChars;
	if (lastChar <= 0) {
	    break;
	}
	chunkPtr++;
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_UnderlineTextLayout --
 *
 *	Use the information in the Tk_TextLayout token to display an
 *	underline below an individual character.  This procedure does
 *	not draw the text, just the underline.
 *
 *	This procedure is useful for simple widgets that need to
 *	display single-font, multi-line text with an individual
 *	character underlined and want Tk to handle the details.
 *	To display larger amounts of underlined text, construct
 *	and use an underlined font.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Underline drawn on the screen.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_UnderlineTextLayout(display, drawable, gc, layout, x, y, underline)
    Display *display;		/* Display on which to draw. */
    Drawable drawable;		/* Window or pixmap in which to draw. */
    GC gc;			/* Graphics context to use for drawing text. */
    Tk_TextLayout layout;	/* Layout information, from a previous call
				 * to Tk_ComputeTextLayout(). */
    int x, y;			/* Upper-left hand corner of rectangle in
				 * which to draw (pixels). */
    int underline;		/* Index of the single character to
				 * underline, or -1 for no underline. */
{
    TextLayout *layoutPtr;
    TkFont *fontPtr;
    int xx, yy, width, height;

    if ((Tk_CharBbox(layout, underline, &xx, &yy, &width, &height) != 0)
	    && (width != 0)) {
	layoutPtr = (TextLayout *) layout;
	fontPtr = (TkFont *) layoutPtr->tkfont;

	XFillRectangle(display, drawable, gc, x + xx, 
		y + yy + fontPtr->fm.ascent + fontPtr->underlinePos,
		(unsigned int) width, (unsigned int) fontPtr->underlineHeight);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_PointToChar --
 *
 *	Use the information in the Tk_TextLayout token to determine the
 *	character closest to the given point.  The point must be
 *	specified with respect to the upper-left hand corner of the
 *	text layout, which is considered to be located at (0, 0).
 *
 *	Any point whose y-value is less that 0 will be considered closest
 *	to the first character in the text layout; any point whose y-value
 *	is greater than the height of the text layout will be considered
 *	closest to the last character in the text layout.
 *
 *	Any point whose x-value is less than 0 will be considered closest
 *	to the first character on that line; any point whose x-value is
 *	greater than the width of the text layout will be considered
 *	closest to the last character on that line.
 *
 * Results:
 *	The return value is the index of the character that was
 *	closest to the point.  Given a text layout with no characters,
 *	the value 0 will always be returned, referring to a hypothetical
 *	zero-width placeholder character.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

int
Tk_PointToChar(layout, x, y)
    Tk_TextLayout layout;	/* Layout information, from a previous call
				 * to Tk_ComputeTextLayout(). */
    int x, y;			/* Coordinates of point to check, with
				 * respect to the upper-left corner of the
				 * text layout. */
{
    TextLayout *layoutPtr;
    LayoutChunk *chunkPtr, *lastPtr;
    TkFont *fontPtr;
    int i, n, dummy, baseline, pos;

    if (y < 0) {
	/*
	 * Point lies above any line in this layout.  Return the index of
	 * the first char.
	 */

	return 0;
    }

    /*
     * Find which line contains the point.
     */

    layoutPtr = (TextLayout *) layout;
    fontPtr = (TkFont *) layoutPtr->tkfont;
    lastPtr = chunkPtr = layoutPtr->chunks;
    for (i = 0; i < layoutPtr->numChunks; i++) {
	baseline = chunkPtr->y;
	if (y < baseline + fontPtr->fm.descent) {
	    if (x < chunkPtr->x) {
		/*
		 * Point is to the left of all chunks on this line.  Return
		 * the index of the first character on this line.
		 */

		return chunkPtr->start - layoutPtr->string;
	    }
	    if (x >= layoutPtr->width) {
		/*
		 * If point lies off right side of the text layout, return
		 * the last char in the last chunk on this line.  Without
		 * this, it might return the index of the first char that
		 * was located outside of the text layout.
		 */

		x = INT_MAX;
	    }

	    /*
	     * Examine all chunks on this line to see which one contains
	     * the specified point.
	     */

	    lastPtr = chunkPtr;
	    while ((i < layoutPtr->numChunks) && (chunkPtr->y == baseline))  {
		if (x < chunkPtr->x + chunkPtr->totalWidth) {
		    /*
		     * Point falls on one of the characters in this chunk.
		     */

		    if (chunkPtr->numDisplayChars < 0) {
			/*
			 * This is a special chunk that encapsulates a single
			 * tab or newline char.
			 */

			return chunkPtr->start - layoutPtr->string;
		    }
		    n = Tk_MeasureChars((Tk_Font) fontPtr, chunkPtr->start,
			    chunkPtr->numChars, x + 1 - chunkPtr->x,
			    TK_PARTIAL_OK, &dummy);
		    return (chunkPtr->start + n - 1) - layoutPtr->string;
		}
		lastPtr = chunkPtr;
		chunkPtr++;
		i++;
	    }

	    /*
	     * Point is to the right of all chars in all the chunks on this
	     * line.  Return the index just past the last char in the last
	     * chunk on this line.
	     */

	    pos = (lastPtr->start + lastPtr->numChars) - layoutPtr->string;
	    if (i < layoutPtr->numChunks) {
		pos--;
	    }
	    return pos;
	}
	lastPtr = chunkPtr;
	chunkPtr++;
    }

    /*
     * Point lies below any line in this text layout.  Return the index
     * just past the last char.
     */

    return (lastPtr->start + lastPtr->numChars) - layoutPtr->string;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_CharBbox --
 *
 *	Use the information in the Tk_TextLayout token to return the
 *	bounding box for the character specified by index.  
 *
 *	The width of the bounding box is the advance width of the
 *	character, and does not include and left- or right-bearing.
 *	Any character that extends partially outside of the
 *	text layout is considered to be truncated at the edge.  Any
 *	character which is located completely outside of the text
 *	layout is considered to be zero-width and pegged against
 *	the edge.
 *
 *	The height of the bounding box is the line height for this font,
 *	extending from the top of the ascent to the bottom of the
 *	descent.  Information about the actual height of the individual
 *	letter is not available.
 *
 *	A text layout that contains no characters is considered to
 *	contain a single zero-width placeholder character.
 * 
 * Results:
 *	The return value is 0 if the index did not specify a character
 *	in the text layout, or non-zero otherwise.  In that case,
 *	*bbox is filled with the bounding box of the character.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

int
Tk_CharBbox(layout, index, xPtr, yPtr, widthPtr, heightPtr)
    Tk_TextLayout layout;   /* Layout information, from a previous call to
			     * Tk_ComputeTextLayout(). */
    int index;		    /* The index of the character whose bbox is
			     * desired. */
    int *xPtr, *yPtr;	    /* Filled with the upper-left hand corner, in
			     * pixels, of the bounding box for the character
			     * specified by index, if non-NULL. */
    int *widthPtr, *heightPtr;
			    /* Filled with the width and height of the
			     * bounding box for the character specified by
			     * index, if non-NULL. */
{
    TextLayout *layoutPtr;
    LayoutChunk *chunkPtr;
    int i, x, w;
    Tk_Font tkfont;
    TkFont *fontPtr;

    if (index < 0) {
	return 0;
    }

    layoutPtr = (TextLayout *) layout;
    chunkPtr = layoutPtr->chunks;
    tkfont = layoutPtr->tkfont;
    fontPtr = (TkFont *) tkfont;

    for (i = 0; i < layoutPtr->numChunks; i++) {
	if (chunkPtr->numDisplayChars < 0) {
	    if (index == 0) {
		x = chunkPtr->x;
		w = chunkPtr->totalWidth;
		goto check;
	    }
	} else if (index < chunkPtr->numChars) {
	    if (xPtr != NULL) {
		Tk_MeasureChars(tkfont, chunkPtr->start, index, 0, 0, &x);
		x += chunkPtr->x;
	    }
	    if (widthPtr != NULL) {
		Tk_MeasureChars(tkfont, chunkPtr->start + index, 1, 0, 0, &w);
	    }
	    goto check;
	}
	index -= chunkPtr->numChars;
	chunkPtr++;
    }
    if (index == 0) {
	/*
	 * Special case to get location just past last char in layout.
	 */

	chunkPtr--;
	x = chunkPtr->x + chunkPtr->totalWidth;
	w = 0;
    } else {
	return 0;
    }

    /*
     * Ensure that the bbox lies within the text layout.  This forces all
     * chars that extend off the right edge of the text layout to have
     * truncated widths, and all chars that are completely off the right
     * edge of the text layout to peg to the edge and have 0 width.
     */
    check:
    if (yPtr != NULL) {
	*yPtr = chunkPtr->y - fontPtr->fm.ascent;
    }
    if (heightPtr != NULL) {
	*heightPtr = fontPtr->fm.ascent + fontPtr->fm.descent;
    }

    if (x > layoutPtr->width) {
	x = layoutPtr->width;
    }
    if (xPtr != NULL) {
	*xPtr = x;
    }
    if (widthPtr != NULL) {
	if (x + w > layoutPtr->width) {
	    w = layoutPtr->width - x;
	}
	*widthPtr = w;
    }

    return 1;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_DistanceToTextLayout --
 *
 *	Computes the distance in pixels from the given point to the
 *	given text layout.  Non-displaying space characters that occur
 *	at the end of individual lines in the text layout are ignored
 *	for hit detection purposes.
 *
 * Results:
 *	The return value is 0 if the point (x, y) is inside the text
 *	layout.  If the point isn't inside the text layout then the
 *	return value is the distance in pixels from the point to the
 *	text item.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

int
Tk_DistanceToTextLayout(layout, x, y)
    Tk_TextLayout layout;	/* Layout information, from a previous call
				 * to Tk_ComputeTextLayout(). */
    int x, y;			/* Coordinates of point to check, with
				 * respect to the upper-left corner of the
				 * text layout (in pixels). */
{
    int i, x1, x2, y1, y2, xDiff, yDiff, dist, minDist, ascent, descent;
    LayoutChunk *chunkPtr;
    TextLayout *layoutPtr;
    TkFont *fontPtr;

    layoutPtr = (TextLayout *) layout;
    fontPtr = (TkFont *) layoutPtr->tkfont;
    ascent = fontPtr->fm.ascent;
    descent = fontPtr->fm.descent;
    
    minDist = 0;
    chunkPtr = layoutPtr->chunks;
    for (i = 0; i < layoutPtr->numChunks; i++) {
	if (chunkPtr->start[0] == '\n') {
	    /*
	     * Newline characters are not counted when computing distance
	     * (but tab characters would still be considered).
	     */

	    chunkPtr++;
	    continue;
	}

	x1 = chunkPtr->x;
	y1 = chunkPtr->y - ascent;
	x2 = chunkPtr->x + chunkPtr->displayWidth;
	y2 = chunkPtr->y + descent;

	if (x < x1) {
	    xDiff = x1 - x;
	} else if (x >= x2) {
	    xDiff = x - x2 + 1;
	} else {
	    xDiff = 0;
	}

	if (y < y1) {
	    yDiff = y1 - y;
	} else if (y >= y2) {
	    yDiff = y - y2 + 1;
	} else {
	    yDiff = 0;
	}
	if ((xDiff == 0) && (yDiff == 0)) {
	    return 0;
	}
	dist = (int) hypot((double) xDiff, (double) yDiff);
	if ((dist < minDist) || (minDist == 0)) {
	    minDist = dist;
	}
	chunkPtr++;
    }
    return minDist;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_IntersectTextLayout --
 *
 *	Determines whether a text layout lies entirely inside,
 *	entirely outside, or overlaps a given rectangle.  Non-displaying
 *	space characters that occur at the end of individual lines in
 *	the text layout are ignored for intersection calculations.
 *
 * Results:
 *	The return value is -1 if the text layout is entirely outside of
 *	the rectangle, 0 if it overlaps, and 1 if it is entirely inside
 *	of the rectangle.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

int
Tk_IntersectTextLayout(layout, x, y, width, height)
    Tk_TextLayout layout;	/* Layout information, from a previous call
				 * to Tk_ComputeTextLayout(). */
    int x, y;			/* Upper-left hand corner, in pixels, of
				 * rectangular area to compare with text
				 * layout.  Coordinates are with respect to
				 * the upper-left hand corner of the text
				 * layout itself. */
    int width, height;		/* The width and height of the above
				 * rectangular area, in pixels. */
{
    int result, i, x1, y1, x2, y2;
    TextLayout *layoutPtr;
    LayoutChunk *chunkPtr;
    TkFont *fontPtr;
    int left, top, right, bottom;

    /*
     * Scan the chunks one at a time, seeing whether each is entirely in,
     * entirely out, or overlapping the rectangle.  If an overlap is
     * detected, return immediately; otherwise wait until all chunks have
     * been processed and see if they were all inside or all outside.
     */
    
    layoutPtr = (TextLayout *) layout;
    chunkPtr = layoutPtr->chunks;
    fontPtr = (TkFont *) layoutPtr->tkfont;

    left    = x;
    top	    = y;
    right   = x + width;
    bottom  = y + height;

    result = 0;
    for (i = 0; i < layoutPtr->numChunks; i++) {
	if (chunkPtr->start[0] == '\n') {
	    /*
	     * Newline characters are not counted when computing area
	     * intersection (but tab characters would still be considered).
	     */

	    chunkPtr++;
	    continue;
	}

	x1 = chunkPtr->x;
	y1 = chunkPtr->y - fontPtr->fm.ascent;
	x2 = chunkPtr->x + chunkPtr->displayWidth;
	y2 = chunkPtr->y + fontPtr->fm.descent;

	if ((right < x1) || (left >= x2)
		|| (bottom < y1) || (top >= y2)) {
	    if (result == 1) {
		return 0;
	    }
	    result = -1;
	} else if ((x1 < left) || (x2 >= right)
		|| (y1 < top) || (y2 >= bottom)) {
	    return 0;
	} else if (result == -1) {
	    return 0;
	} else {
	    result = 1;
	}
	chunkPtr++;
    }
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_TextLayoutToPostscript --
 *
 *	Outputs the contents of a text layout in Postscript format.
 *	The set of lines in the text layout will be rendered by the user
 *	supplied Postscript function.  The function should be of the form:
 *
 *	    justify x y string  function  --
 *
 *	Justify is -1, 0, or 1, depending on whether the following string
 *	should be left, center, or right justified, x and y is the
 *	location for the origin of the string, string is the sequence
 *	of characters to be printed, and function is the name of the
 *	caller-provided function; the function should leave nothing
 *	on the stack.
 *
 *	The meaning of the origin of the string (x and y) depends on
 *	the justification.  For left justification, x is where the
 *	left edge of the string should appear.  For center justification,
 *	x is where the center of the string should appear.  And for right
 *	justification, x is where the right edge of the string should
 *	appear.  This behavior is necessary because, for example, right
 *	justified text on the screen is justified with screen metrics.
 *	The same string needs to be justified with printer metrics on
 *	the printer to appear in the correct place with respect to other
 *	similarly justified strings.  In all circumstances, y is the
 *	location of the baseline for the string.
 *
 * Results:
 *	Interp->result is modified to hold the Postscript code that
 *	will render the text layout.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_TextLayoutToPostscript(interp, layout)
    Tcl_Interp *interp;		/* Filled with Postscript code. */
    Tk_TextLayout layout;	/* The layout to be rendered. */
{
#define MAXUSE 128
    char buf[MAXUSE+10];
    LayoutChunk *chunkPtr;
    int i, j, used, c, baseline;
    TextLayout *layoutPtr;

    layoutPtr = (TextLayout *) layout;
    chunkPtr = layoutPtr->chunks;
    baseline = chunkPtr->y;
    used = 0;
    buf[used++] = '(';
    for (i = 0; i < layoutPtr->numChunks; i++) {
	if (baseline != chunkPtr->y) {
	    buf[used++] = ')';
	    buf[used++] = '\n';
	    buf[used++] = '(';
	    baseline = chunkPtr->y;
	}
	if (chunkPtr->numDisplayChars <= 0) {
	    if (chunkPtr->start[0] == '\t') {
		buf[used++] = '\\';
		buf[used++] = 't';
	    }
	} else {
	    for (j = 0; j < chunkPtr->numDisplayChars; j++) {
		c = UCHAR(chunkPtr->start[j]);
		if ((c == '(') || (c == ')') || (c == '\\') || (c < 0x20)
			|| (c >= UCHAR(0x7f))) {
		    /*
		     * Tricky point:  the "03" is necessary in the sprintf
		     * below, so that a full three digits of octal are
		     * always generated.  Without the "03", a number
		     * following this sequence could be interpreted by
		     * Postscript as part of this sequence.
		     */

		    sprintf(buf + used, "\\%03o", c);
		    used += 4;
		} else {
		    buf[used++] = c;
		}
		if (used >= MAXUSE) {
		    buf[used] = '\0';
		    Tcl_AppendResult(interp, buf, (char *) NULL);
		    used = 0;
		}
	    }
	}
	if (used >= MAXUSE) {
	    /*
	     * If there are a whole bunch of returns or tabs in a row,
	     * then buf[] could get filled up.
	     */
	     
	    buf[used] = '\0';
	    Tcl_AppendResult(interp, buf, (char *) NULL);
	    used = 0;
	}
	chunkPtr++;
    }
    buf[used++] = ')';
    buf[used++] = '\n';
    buf[used] = '\0';
    Tcl_AppendResult(interp, buf, (char *) NULL);
}

/*
 *---------------------------------------------------------------------------
 *
 * TkInitFontAttributes --
 *
 *	Initialize the font attributes structure to contain sensible
 *	values.  This must be called before using any other font
 *	attributes functions.
 *
 * Results:
 *	None.
 *
 * Side effects.
 *	None.
 *
 *---------------------------------------------------------------------------
 */

void
TkInitFontAttributes(faPtr)
    TkFontAttributes *faPtr;	/* The attributes structure to initialize. */
{
    faPtr->family	= NULL;
    faPtr->pointsize	= 0;
    faPtr->weight	= TK_FW_NORMAL;
    faPtr->slant	= TK_FS_ROMAN;
    faPtr->underline	= 0;
    faPtr->overstrike	= 0;
}

/*
 *---------------------------------------------------------------------------
 *
 * ConfigAttributesObj --
 *
 *	Process command line options to fill in fields of a properly
 *	initialized font attributes structure.
 *
 * Results:
 *	A standard Tcl return value.  If TCL_ERROR is returned, an
 *	error message will be left in interp's result object.
 *
 * Side effects:
 *	The fields of the font attributes structure get filled in with
 *	information from argc/argv.  If an error occurs while parsing,
 *	the font attributes structure will contain all modifications
 *	specified in the command line options up to the point of the
 *	error.
 *
 *---------------------------------------------------------------------------
 */

static int
ConfigAttributesObj(interp, tkwin, objc, objv, faPtr)
    Tcl_Interp *interp;		/* Interp for error return. */
    Tk_Window tkwin;		/* For display on which font will be used. */
    int objc;			/* Number of elements in argv. */
    Tcl_Obj *CONST objv[];	/* Command line options. */
    TkFontAttributes *faPtr;	/* Font attributes structure whose fields
				 * are to be modified.  Structure must already
				 * be properly initialized. */
{
    int i, n, index;
    Tcl_Obj *value;
    char *option, *string;
    
    if (objc & 1) {
	string = Tcl_GetStringFromObj(objv[objc - 1], NULL);
	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "missing value for \"",
		string, "\" option", (char *) NULL);
	return TCL_ERROR;
    }

    for (i = 0; i < objc; i += 2) {
	option = Tcl_GetStringFromObj(objv[i], NULL);
	value = objv[i + 1];

	if (Tcl_GetIndexFromObj(interp, objv[i], fontOpt, "option", 1,
		&index) != TCL_OK) {
	    return TCL_ERROR;
	}
	switch (index) {
	    case FONT_FAMILY:
		string = Tcl_GetStringFromObj(value, NULL);
		faPtr->family = Tk_GetUid(string);
		break;

	    case FONT_SIZE:
		if (Tcl_GetIntFromObj(interp, value, &n) != TCL_OK) {
		    return TCL_ERROR;
		}
		faPtr->pointsize = n;
		break;

	    case FONT_WEIGHT:
		string = Tcl_GetStringFromObj(value, NULL);
		n = TkFindStateNum(interp, option, weightMap, string);
		if (n == TK_FW_UNKNOWN) {
		    return TCL_ERROR;
		}
		faPtr->weight = n;
		break;

	    case FONT_SLANT: 
		string = Tcl_GetStringFromObj(value, NULL);
		n = TkFindStateNum(interp, option, slantMap, string);
		if (n == TK_FS_UNKNOWN) {
		    return TCL_ERROR;
		}
		faPtr->slant = n;
		break;

	    case FONT_UNDERLINE:
		if (Tcl_GetBooleanFromObj(interp, value, &n) != TCL_OK) {
		    return TCL_ERROR;
		}
		faPtr->underline = n;
		break;

	    case FONT_OVERSTRIKE:
		if (Tcl_GetBooleanFromObj(interp, value, &n) != TCL_OK) {
		    return TCL_ERROR;
		}
		faPtr->overstrike = n;
		break;
	}
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetAttributeInfoObj --
 *
 *	Return information about the font attributes as a Tcl list.
 *
 * Results:
 *	The return value is TCL_OK if the objPtr was non-NULL and
 *	specified a valid font attribute, TCL_ERROR otherwise.  If TCL_OK
 *	is returned, the interp's result object is modified to hold a
 *	description of either the current value of a single option, or a
 *	list of all options and their current values for the given font
 *	attributes.  If TCL_ERROR is returned, the interp's result is
 *	set to an error message describing that the objPtr did not refer
 *	to a valid option.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static int
GetAttributeInfoObj(interp, faPtr, objPtr)
    Tcl_Interp *interp;		  	/* Interp to hold result. */
    CONST TkFontAttributes *faPtr;	/* The font attributes to inspect. */
    Tcl_Obj *objPtr;		  	/* If non-NULL, indicates the single
					 * option whose value is to be
					 * returned. Otherwise
					 * information is returned for
					 * all options. */
{
    int i, index, start, end, num;
    char *str;
    Tcl_Obj *newPtr;

    start = 0;
    end = FONT_NUMFIELDS;
    if (objPtr != NULL) {
	if (Tcl_GetIndexFromObj(interp, objPtr, fontOpt, "option", 1,
		&index) != TCL_OK) {
	    return TCL_ERROR;
	}
	start = index;
	end = index + 1;
    }

    for (i = start; i < end; i++) {
	str = NULL;
	num = 0;			/* Needed only to prevent compiler
					 * warning. */
	switch (i) {
	    case FONT_FAMILY:
		str = faPtr->family;
		if (str == NULL) {
		    str = "";
		}
		break;

	    case FONT_SIZE:
		num = faPtr->pointsize;
		break;

	    case FONT_WEIGHT:
		str = TkFindStateString(weightMap, faPtr->weight);
		break;
	
	    case FONT_SLANT:
		str = TkFindStateString(slantMap, faPtr->slant);
		break;

	    case FONT_UNDERLINE:
		num = faPtr->underline;
		break;

	    case FONT_OVERSTRIKE:
		num = faPtr->overstrike;
		break;
	}
	if (objPtr == NULL) {
	    Tcl_ListObjAppendElement(NULL, Tcl_GetObjResult(interp),
		    Tcl_NewStringObj(fontOpt[i], -1));
	    if (str != NULL) {
		newPtr = Tcl_NewStringObj(str, -1);
	    } else {
		newPtr = Tcl_NewIntObj(num);
	    }
	    Tcl_ListObjAppendElement(NULL, Tcl_GetObjResult(interp),
		    newPtr);
	} else {
	    if (str != NULL) {
		Tcl_SetStringObj(Tcl_GetObjResult(interp), str, -1);
	    } else {
		Tcl_SetIntObj(Tcl_GetObjResult(interp), num);
	    }
	}
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ParseFontNameObj --
 *
 *	Converts a object into a set of font attributes that can be used
 *	to construct a font.
 *
 *	The string rep of the object can be one of the following forms:
 *		XLFD (see X documentation)
 *		"Family [size [style] [style ...]]"
 *		"-option value [-option value ...]"
 *
 * Results:
 *	The return value is TCL_ERROR if the object was syntactically
 *	invalid.  In that case an error message is left in interp's
 *	result object.  Otherwise, fills the font attribute buffer with
 *	the values parsed from the string and returns TCL_OK;
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static int
ParseFontNameObj(interp, tkwin, objPtr, faPtr)
    Tcl_Interp *interp;		/* Interp for error return. */
    Tk_Window tkwin;		/* For display on which font is used. */
    Tcl_Obj *objPtr;		/* Parseable font description object. */
    TkFontAttributes *faPtr;	/* Font attributes structure whose fields
				 * are to be modified.  Structure must already
				 * be properly initialized. */
{
    char *dash;
    int objc, result, i, n;
    Tcl_Obj **objv;
    TkXLFDAttributes xa;
    char *string;
    
    string = Tcl_GetStringFromObj(objPtr, NULL);
    if (*string == '-') {
	/*
	 * This may be an XLFD or an "-option value" string.
	 *
	 * If the string begins with "-*" or a "-foundry-family-*" pattern,
	 * then consider it an XLFD.  
	 */

	if (string[1] == '*') {
	    goto xlfd;
	}
	dash = strchr(string + 1, '-');
	if ((dash != NULL) && (!isspace(UCHAR(dash[-1])))) {
	    goto xlfd;
	}

	if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
	    return TCL_ERROR;
	}

	return ConfigAttributesObj(interp, tkwin, objc, objv, faPtr);
    }
    
    if (*string == '*') {
	/*
	 * This appears to be an XLFD.
	 */

	xlfd:
	xa.fa = *faPtr;
	result = TkParseXLFD(string, &xa);
	if (result == TCL_OK) {
	    *faPtr = xa.fa;
	    return result;
	}
    }

    /*
     * Wasn't an XLFD or "-option value" string.  Try it as a
     * "font size style" list.
     */

    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
	return TCL_ERROR;
    }
    if (objc < 1) {
	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "font \"", string,
		"\" doesn't exist", (char *) NULL);
	return TCL_ERROR;
    }

    faPtr->family = Tk_GetUid(Tcl_GetStringFromObj(objv[0], NULL));
    if (objc > 1) {
	if (Tcl_GetIntFromObj(interp, objv[1], &n) != TCL_OK) {
	    return TCL_ERROR;
	}
	faPtr->pointsize = n;
    }

    i = 2;
    if (objc == 3) {
	if (Tcl_ListObjGetElements(interp, objv[2], &objc, &objv) != TCL_OK) {
	    return TCL_ERROR;
	}
	i = 0;
    }
    for ( ; i < objc; i++) {
	string = Tcl_GetStringFromObj(objv[i], NULL);
	n = TkFindStateNum(NULL, NULL, weightMap, string);
	if (n != TK_FW_UNKNOWN) {
	    faPtr->weight = n;
	    continue;
	}
	n = TkFindStateNum(NULL, NULL, slantMap, string);
	if (n != TK_FS_UNKNOWN) {
	    faPtr->slant = n;
	    continue;
	}
	n = TkFindStateNum(NULL, NULL, underlineMap, string);
	if (n != 0) {
	    faPtr->underline = n;
	    continue;
	}
	n = TkFindStateNum(NULL, NULL, overstrikeMap, string);
	if (n != 0) {
	    faPtr->overstrike = n;
	    continue;
	}

	/*
	 * Unknown style.
	 */

	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
		"unknown font style \"", string, "\"",
		(char *) NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TkParseXLFD --
 *
 *	Break up a fully specified XLFD into a set of font attributes.
 *
 * Results:
 *	Return value is TCL_ERROR if string was not a fully specified XLFD.
 *	Otherwise, fills font attribute buffer with the values parsed
 *	from the XLFD and returns TCL_OK.  
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

int
TkParseXLFD(string, xaPtr)
    CONST char *string;		/* Parseable font description string. */
    TkXLFDAttributes *xaPtr;	/* XLFD attributes structure whose fields
				 * are to be modified.  Structure must already
				 * be properly initialized. */
{
    char *src;
    CONST char *str;
    int i, j;
    char *field[XLFD_NUMFIELDS + 2];
    Tcl_DString ds;
    
    memset(field, '\0', sizeof(field));

    str = string;
    if (*str == '-') {
	str++;
    }

    Tcl_DStringInit(&ds);
    Tcl_DStringAppend(&ds, (char *) str, -1);
    src = Tcl_DStringValue(&ds);

    field[0] = src;
    for (i = 0; *src != '\0'; src++) {
	if (isupper(UCHAR(*src))) {
	    *src = tolower(UCHAR(*src));
	}
	if (*src == '-') {
	    i++;
	    if (i > XLFD_NUMFIELDS) {
		break;
	    }
	    *src = '\0';
	    field[i] = src + 1;
	}
    }

    /*
     * An XLFD of the form -adobe-times-medium-r-*-12-*-* is pretty common, 
     * but it is (strictly) malformed, because the first * is eliding both
     * the Setwidth and the Addstyle fields.  If the Addstyle field is a
     * number, then assume the above incorrect form was used and shift all
     * the rest of the fields up by one, so the number gets interpreted
     * as a pixelsize.  This fix is so that we don't get a million reports
     * that "it works under X, but gives a syntax error under Windows".
     */

    if ((i > XLFD_ADD_STYLE) && (FieldSpecified(field[XLFD_ADD_STYLE]))) {
	if (atoi(field[XLFD_ADD_STYLE]) != 0) {
	    for (j = XLFD_NUMFIELDS - 1; j >= XLFD_ADD_STYLE; j--) {
		field[j + 1] = field[j];
	    }
	    field[XLFD_ADD_STYLE] = NULL;
	    i++;
	}
    }

    /*
     * Bail if we don't have enough of the fields (up to pointsize).
     */

    if (i < XLFD_FAMILY) {
	Tcl_DStringFree(&ds);
	return TCL_ERROR;
    }

    if (FieldSpecified(field[XLFD_FOUNDRY])) {
	xaPtr->foundry = Tk_GetUid(field[XLFD_FOUNDRY]);
    }

    if (FieldSpecified(field[XLFD_FAMILY])) {
	xaPtr->fa.family = Tk_GetUid(field[XLFD_FAMILY]);
    }
    if (FieldSpecified(field[XLFD_WEIGHT])) {
	xaPtr->fa.weight = TkFindStateNum(NULL, NULL, xlfdWeightMap,
		field[XLFD_WEIGHT]);
    }
    if (FieldSpecified(field[XLFD_SLANT])) {
	xaPtr->slant = TkFindStateNum(NULL, NULL, xlfdSlantMap,
		field[XLFD_SLANT]);
	if (xaPtr->slant == TK_FS_ROMAN) {
	    xaPtr->fa.slant = TK_FS_ROMAN;
	} else {
	    xaPtr->fa.slant = TK_FS_ITALIC;
	}
    }
    if (FieldSpecified(field[XLFD_SETWIDTH])) {
	xaPtr->setwidth = TkFindStateNum(NULL, NULL, xlfdSetwidthMap,
		field[XLFD_SETWIDTH]);
    }

    /* XLFD_ADD_STYLE ignored. */

    /*
     * Pointsize in tenths of a point, but treat it as tenths of a pixel.
     */

    if (FieldSpecified(field[XLFD_POINT_SIZE])) {
	if (field[XLFD_POINT_SIZE][0] == '[') {
	    /*
	     * Some X fonts have the point size specified as follows:
	     *
	     *	    [ N1 N2 N3 N4 ]
	     *
	     * where N1 is the point size (in points, not decipoints!), and
	     * N2, N3, and N4 are some additional numbers that I don't know
	     * the purpose of, so I ignore them.
	     */

	    xaPtr->fa.pointsize = atoi(field[XLFD_POINT_SIZE] + 1);
	} else if (Tcl_GetInt(NULL, field[XLFD_POINT_SIZE],
		&xaPtr->fa.pointsize) == TCL_OK) {
	    xaPtr->fa.pointsize /= 10;
	} else {
	    return TCL_ERROR;
	}
    }

    /*
     * Pixel height of font.  If specified, overrides pointsize.
     */

    if (FieldSpecified(field[XLFD_PIXEL_SIZE])) {
	if (field[XLFD_PIXEL_SIZE][0] == '[') {
	    /*
	     * Some X fonts have the pixel size specified as follows:
	     *
	     *	    [ N1 N2 N3 N4 ]
	     *
	     * where N1 is the pixel size, and where N2, N3, and N4 
	     * are some additional numbers that I don't know
	     * the purpose of, so I ignore them.
	     */

	    xaPtr->fa.pointsize = atoi(field[XLFD_PIXEL_SIZE] + 1);
	} else if (Tcl_GetInt(NULL, field[XLFD_PIXEL_SIZE],
		&xaPtr->fa.pointsize) != TCL_OK) {
	    return TCL_ERROR;
	}
    }

    xaPtr->fa.pointsize = -xaPtr->fa.pointsize;

    /* XLFD_RESOLUTION_X ignored. */

    /* XLFD_RESOLUTION_Y ignored. */

    /* XLFD_SPACING ignored. */

    /* XLFD_AVERAGE_WIDTH ignored. */

    if (FieldSpecified(field[XLFD_REGISTRY])) {
	xaPtr->charset = TkFindStateNum(NULL, NULL, xlfdCharsetMap,
		field[XLFD_REGISTRY]);
    }
    if (FieldSpecified(field[XLFD_ENCODING])) {
	xaPtr->encoding = atoi(field[XLFD_ENCODING]);
    }

    Tcl_DStringFree(&ds);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * FieldSpecified --
 *
 *	Helper function for TkParseXLFD().  Determines if a field in the
 *	XLFD was set to a non-null, non-don't-care value.
 *
 * Results:
 *	The return value is 0 if the field in the XLFD was not set and
 *	should be ignored, non-zero otherwise.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static int
FieldSpecified(field)
    CONST char *field;	/* The field of the XLFD to check.  Strictly
			 * speaking, only when the string is "*" does it mean
			 * don't-care.  However, an unspecified or question
			 * mark is also interpreted as don't-care. */
{
    char ch;

    if (field == NULL) {
	return 0;
    }
    ch = field[0];
    return (ch != '*' && ch != '?');
}

/*
 *---------------------------------------------------------------------------
 *
 * NewChunk --
 *
 *	Helper function for Tk_ComputeTextLayout().  Encapsulates a
 *	measured set of characters in a chunk that can be quickly
 *	drawn.
 *
 * Results:
 *	A pointer to the new chunk in the text layout.
 *
 * Side effects:
 *	The text layout is reallocated to hold more chunks as necessary.
 *
 *	Currently, Tk_ComputeTextLayout() stores contiguous ranges of
 *	"normal" characters in a chunk, along with individual tab
 *	and newline chars in their own chunks.  All characters in the
 *	text layout are accounted for.
 *
 *---------------------------------------------------------------------------
 */
static LayoutChunk *
NewChunk(layoutPtrPtr, maxPtr, start, numChars, curX, newX, y)
    TextLayout **layoutPtrPtr;
    int *maxPtr;
    CONST char *start;
    int numChars;
    int curX;
    int newX;
    int y;
{
    TextLayout *layoutPtr;
    LayoutChunk *chunkPtr;
    int maxChunks;
    size_t s;
    
    layoutPtr = *layoutPtrPtr;
    maxChunks = *maxPtr;
    if (layoutPtr->numChunks == maxChunks) {
	maxChunks *= 2;
	s = sizeof(TextLayout) + ((maxChunks - 1) * sizeof(LayoutChunk));
	layoutPtr = (TextLayout *) ckrealloc((char *) layoutPtr, s);

	*layoutPtrPtr = layoutPtr;
	*maxPtr = maxChunks;
    }
    chunkPtr = &layoutPtr->chunks[layoutPtr->numChunks];
    chunkPtr->start		= start;
    chunkPtr->numChars		= numChars;
    chunkPtr->numDisplayChars	= numChars;
    chunkPtr->x			= curX;
    chunkPtr->y			= y;
    chunkPtr->totalWidth	= newX - curX;
    chunkPtr->displayWidth	= newX - curX;
    layoutPtr->numChunks++;

    return chunkPtr;
}

