/*
 * tkUnixFont.c --
 *
 *	Contains the Unix implementation of the platform-independent font
 *	package interface.
 *
 * Copyright (c) 1996-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: tkUnixFont.c,v 1.33 2007/02/28 09:23:30 dkf Exp $
 */

#include "tkUnixInt.h"
#include "tkFont.h"
#include <netinet/in.h>		/* for htons() prototype */
#include <arpa/inet.h>		/* inet_ntoa() */

/*
 * The preferred font encodings.
 */

static CONST char *encodingList[] = {
    "iso8859-1", "jis0208", "jis0212", NULL
};

/*
 * The following structure represents a font family. It is assumed that all
 * screen fonts constructed from the same "font family" share certain
 * properties; all screen fonts with the same "font family" point to a shared
 * instance of this structure. The most important shared property is the
 * character existence metrics, used to determine if a screen font can display
 * a given Unicode character.
 *
 * Under Unix, there are three attributes that uniquely identify a "font
 * family": the foundry, face name, and charset.
 */

#define FONTMAP_SHIFT		10

#define FONTMAP_PAGES		(1 << (sizeof(Tcl_UniChar)*8 - FONTMAP_SHIFT))
#define FONTMAP_BITSPERPAGE	(1 << FONTMAP_SHIFT)

typedef struct FontFamily {
    struct FontFamily *nextPtr;	/* Next in list of all known font families. */
    int refCount;		/* How many SubFonts are referring to this
				 * FontFamily. When the refCount drops to
				 * zero, this FontFamily may be freed. */
    /*
     * Key.
     */

    Tk_Uid foundry;		/* Foundry key for this FontFamily. */
    Tk_Uid faceName;		/* Face name key for this FontFamily. */
    Tcl_Encoding encoding;	/* Encoding key for this FontFamily. */

    /*
     * Derived properties.
     */

    int isTwoByteFont;		/* 1 if this is a double-byte font, 0
				 * otherwise. */
    char *fontMap[FONTMAP_PAGES];
				/* Two-level sparse table used to determine
				 * quickly if the specified character exists.
				 * As characters are encountered, more pages
				 * in this table are dynamically alloced. The
				 * contents of each page is a bitmask
				 * consisting of FONTMAP_BITSPERPAGE bits,
				 * representing whether this font can be used
				 * to display the given character at the
				 * corresponding bit position. The high bits
				 * of the character are used to pick which
				 * page of the table is used. */
} FontFamily;

/*
 * The following structure encapsulates an individual screen font. A font
 * object is made up of however many SubFonts are necessary to display a
 * stream of multilingual characters.
 */

typedef struct SubFont {
    char **fontMap;		/* Pointer to font map from the FontFamily,
				 * cached here to save a dereference. */
    XFontStruct *fontStructPtr;	/* The specific screen font that will be used
				 * when displaying/measuring chars belonging
				 * to the FontFamily. */
    FontFamily *familyPtr;	/* The FontFamily for this SubFont. */
} SubFont;

/*
 * The following structure represents Unix's implementation of a font object.
 */

#define SUBFONT_SPACE		3
#define BASE_CHARS		256

typedef struct UnixFont {
    TkFont font;		/* Stuff used by generic font package. Must be
				 * first in structure. */
    SubFont staticSubFonts[SUBFONT_SPACE];
				/* Builtin space for a limited number of
				 * SubFonts. */
    int numSubFonts;		/* Length of following array. */
    SubFont *subFontArray;	/* Array of SubFonts that have been loaded in
				 * order to draw/measure all the characters
				 * encountered by this font so far. All fonts
				 * start off with one SubFont initialized by
				 * AllocFont() from the original set of font
				 * attributes. Usually points to
				 * staticSubFonts, but may point to malloced
				 * space if there are lots of SubFonts. */
    SubFont controlSubFont;	/* Font to use to display control-character
				 * expansions. */

    Display *display;		/* Display that owns font. */
    int pixelSize;		/* Original pixel size used when font was
				 * constructed. */
    TkXLFDAttributes xa;	/* Additional attributes that specify the
				 * preferred foundry and encoding to use when
				 * constructing additional SubFonts. */
    int widths[BASE_CHARS];	/* Widths of first 256 chars in the base font,
				 * for handling common case. */
    int underlinePos;		/* Offset from baseline to origin of underline
				 * bar (used when drawing underlined font)
				 * (pixels). */
    int barHeight;		/* Height of underline or overstrike bar (used
				 * when drawing underlined or strikeout font)
				 * (pixels). */
} UnixFont;

/*
 * The following structure and definition is used to keep track of the
 * alternative names for various encodings. Asking for an encoding that
 * matches one of the alias patterns will result in actually getting the
 * encoding by its real name.
 */

typedef struct EncodingAlias {
    char *realName;		/* The real name of the encoding to load if
				 * the provided name matched the pattern. */
    char *aliasPattern;		/* Pattern for encoding name, of the form that
				 * is acceptable to Tcl_StringMatch. */
} EncodingAlias;

/*
 * Just some utility structures used for passing around values in helper
 * functions.
 */

typedef struct FontAttributes {
    TkFontAttributes fa;
    TkXLFDAttributes xa;
} FontAttributes;

typedef struct ThreadSpecificData {
    FontFamily *fontFamilyList; /* The list of font families that are
				 * currently loaded. As screen fonts are
				 * loaded, this list grows to hold information
				 * about what characters exist in each font
				 * family. */
    FontFamily controlFamily;	/* FontFamily used to handle control character
				 * expansions. The encoding of this FontFamily
				 * converts UTF-8 to backslashed escape
				 * sequences. */
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;

/*
 * The set of builtin encoding alises to convert the XLFD names for the
 * encodings into the names expected by the Tcl encoding package.
 */

static EncodingAlias encodingAliases[] = {
    {"gb2312-raw",	"gb2312*"},
    {"big5",		"big5*"},
    {"cns11643-1",	"cns11643*-1"},
    {"cns11643-1",	"cns11643*.1-0"},
    {"cns11643-2",	"cns11643*-2"},
    {"cns11643-2",	"cns11643*.2-0"},
    {"jis0201",		"jisx0201*"},
    {"jis0201",		"jisx0202*"},
    {"jis0208",		"jisc6226*"},
    {"jis0208",		"jisx0208*"},
    {"jis0212",		"jisx0212*"},
    {"tis620",		"tis620*"},
    {"ksc5601",		"ksc5601*"},
    {"dingbats",	"*dingbats"},
#ifdef WORDS_BIGENDIAN
    {"unicode",		"iso10646-1"},
#else
    /*
     * ucs-2be is needed if native order isn't BE.
     */
    {"ucs-2be",		"iso10646-1"},
#endif
    {NULL,		NULL}
};

/*
 * Functions used only in this file.
 */

static void		FontPkgCleanup(ClientData clientData);
static FontFamily *	AllocFontFamily(Display *display,
			    XFontStruct *fontStructPtr, int base);
static SubFont *	CanUseFallback(UnixFont *fontPtr,
			    CONST char *fallbackName, int ch,
			    SubFont **fixSubFontPtrPtr);
static SubFont *	CanUseFallbackWithAliases(UnixFont *fontPtr,
			    char *fallbackName, int ch,
			    Tcl_DString *nameTriedPtr,
			    SubFont **fixSubFontPtrPtr);
static int		ControlUtfProc(ClientData clientData, CONST char *src,
			    int srcLen, int flags, Tcl_EncodingState*statePtr,
			    char *dst, int dstLen, int *srcReadPtr,
			    int *dstWrotePtr, int *dstCharsPtr);
static XFontStruct *	CreateClosestFont(Tk_Window tkwin,
			    CONST TkFontAttributes *faPtr,
			    CONST TkXLFDAttributes *xaPtr);
static SubFont *	FindSubFontForChar(UnixFont *fontPtr, int ch,
			    SubFont **fixSubFontPtrPtr);
static void		FontMapInsert(SubFont *subFontPtr, int ch);
static void		FontMapLoadPage(SubFont *subFontPtr, int row);
static int		FontMapLookup(SubFont *subFontPtr, int ch);
static void		FreeFontFamily(FontFamily *afPtr);
static CONST char *	GetEncodingAlias(CONST char *name);
static int		GetFontAttributes(Display *display,
			    XFontStruct *fontStructPtr, FontAttributes *faPtr);
static XFontStruct *	GetScreenFont(Display *display,
			    FontAttributes *wantPtr, char **nameList,
			    int bestIdx[], unsigned int bestScore[]);
static XFontStruct *	GetSystemFont(Display *display);
static int		IdentifySymbolEncodings(FontAttributes *faPtr);
static void		InitFont(Tk_Window tkwin, XFontStruct *fontStructPtr,
			    UnixFont *fontPtr);
static void		InitSubFont(Display *display,
			    XFontStruct *fontStructPtr, int base,
			    SubFont *subFontPtr);
static char **		ListFonts(Display *display, CONST char *faceName,
			    int *numNamesPtr);
static char **		ListFontOrAlias(Display *display, CONST char*faceName,
			    int *numNamesPtr);
static unsigned int	RankAttributes(FontAttributes *wantPtr,
			    FontAttributes *gotPtr);
static void		ReleaseFont(UnixFont *fontPtr);
static void		ReleaseSubFont(Display *display, SubFont *subFontPtr);
static int		SeenName(CONST char *name, Tcl_DString *dsPtr);
#ifndef WORDS_BIGENDIAN
static int		Ucs2beToUtfProc(ClientData clientData, CONST char*src,
			    int srcLen, int flags, Tcl_EncodingState*statePtr,
			    char *dst, int dstLen, int *srcReadPtr,
			    int *dstWrotePtr, int *dstCharsPtr);
static int		UtfToUcs2beProc(ClientData clientData, CONST char*src,
			    int srcLen, int flags, Tcl_EncodingState*statePtr,
			    char *dst, int dstLen, int *srcReadPtr,
			    int *dstWrotePtr, int *dstCharsPtr);
#endif

/*
 *-------------------------------------------------------------------------
 *
 * FontPkgCleanup --
 *
 *	This function is called when an application is created. It initializes
 *	all the structures that are used by the platform-dependent code on a
 *	per application basis.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Releases thread-specific resources used by font pkg.
 *
 *-------------------------------------------------------------------------
 */

static void
FontPkgCleanup(
    ClientData clientData)
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    if (tsdPtr->controlFamily.encoding != NULL) {
	FontFamily *familyPtr = &tsdPtr->controlFamily;
	int i;

	Tcl_FreeEncoding(familyPtr->encoding);
	for (i = 0; i < FONTMAP_PAGES; i++) {
	    if (familyPtr->fontMap[i] != NULL) {
		ckfree(familyPtr->fontMap[i]);
	    }
	}
	tsdPtr->controlFamily.encoding = NULL;
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * TkpFontPkgInit --
 *
 *	This function is called when an application is created. It initializes
 *	all the structures that are used by the platform-dependent code on a
 *	per application basis.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *-------------------------------------------------------------------------
 */

void
TkpFontPkgInit(
    TkMainInfo *mainPtr)	/* The application being created. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    Tcl_EncodingType type;
    SubFont dummy;
    int i;

    if (tsdPtr->controlFamily.encoding == NULL) {
	type.encodingName = "X11ControlChars";
	type.toUtfProc = ControlUtfProc;
	type.fromUtfProc = ControlUtfProc;
	type.freeProc = NULL;
	type.clientData = NULL;
	type.nullSize = 0;

	tsdPtr->controlFamily.refCount = 2;
	tsdPtr->controlFamily.encoding = Tcl_CreateEncoding(&type);
	tsdPtr->controlFamily.isTwoByteFont = 0;

	dummy.familyPtr = &tsdPtr->controlFamily;
	dummy.fontMap = tsdPtr->controlFamily.fontMap;
	for (i = 0x00; i < 0x20; i++) {
	    FontMapInsert(&dummy, i);
	    FontMapInsert(&dummy, i + 0x80);
	}

#ifndef WORDS_BIGENDIAN
	/*
	 * UCS-2BE is unicode (UCS-2) in big-endian format. Define this if
	 * native order isn't BE. It is used in iso10646 fonts.
	 */

	type.encodingName = "ucs-2be";
	type.toUtfProc = Ucs2beToUtfProc;
	type.fromUtfProc = UtfToUcs2beProc;
	type.freeProc = NULL;
	type.clientData = NULL;
	type.nullSize = 2;
	Tcl_CreateEncoding(&type);
#endif
	Tcl_CreateThreadExitHandler(FontPkgCleanup, NULL);
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * ControlUtfProc --
 *
 *	Convert from UTF-8 into the ASCII expansion of a control character.
 *
 * Results:
 *	Returns TCL_OK if conversion was successful.
 *
 * Side effects:
 *	None.
 *
 *-------------------------------------------------------------------------
 */

static int
ControlUtfProc(
    ClientData clientData,	/* Not used. */
    CONST char *src,		/* Source string in UTF-8. */
    int srcLen,			/* Source string length in bytes. */
    int flags,			/* Conversion control flags. */
    Tcl_EncodingState *statePtr,/* Place for conversion routine to store state
				 * information used during a piecewise
				 * conversion. Contents of statePtr are
				 * initialized and/or reset by conversion
				 * routine under control of flags argument. */
    char *dst,			/* Output buffer in which converted string is
				 * stored. */
    int dstLen,			/* The maximum length of output buffer in
				 * bytes. */
    int *srcReadPtr,		/* Filled with the number of bytes from the
				 * source string that were converted. This may
				 * be less than the original source length if
				 * there was a problem converting some source
				 * characters. */
    int *dstWrotePtr,		/* Filled with the number of bytes that were
				 * stored in the output buffer as a result of
				 * the conversion. */
    int *dstCharsPtr)		/* Filled with the number of characters that
				 * correspond to the bytes stored in the
				 * output buffer. */
{
    CONST char *srcStart, *srcEnd;
    char *dstStart, *dstEnd;
    Tcl_UniChar ch;
    int result;
    static char hexChars[] = "0123456789abcdef";
    static char mapChars[] = {
	0, 0, 0, 0, 0, 0, 0,
	'a', 'b', 't', 'n', 'v', 'f', 'r'
    };

    result = TCL_OK;

    srcStart = src;
    srcEnd = src + srcLen;

    dstStart = dst;
    dstEnd = dst + dstLen - 6;

    for ( ; src < srcEnd; ) {
	if (dst > dstEnd) {
	    result = TCL_CONVERT_NOSPACE;
	    break;
	}
	src += Tcl_UtfToUniChar(src, &ch);
	dst[0] = '\\';
	if ((ch < sizeof(mapChars)) && (mapChars[ch] != 0)) {
	    dst[1] = mapChars[ch];
	    dst += 2;
	} else if (ch < 256) {
	    dst[1] = 'x';
	    dst[2] = hexChars[(ch >> 4) & 0xf];
	    dst[3] = hexChars[ch & 0xf];
	    dst += 4;
	} else {
	    dst[1] = 'u';
	    dst[2] = hexChars[(ch >> 12) & 0xf];
	    dst[3] = hexChars[(ch >> 8) & 0xf];
	    dst[4] = hexChars[(ch >> 4) & 0xf];
	    dst[5] = hexChars[ch & 0xf];
	    dst += 6;
	}
    }
    *srcReadPtr = src - srcStart;
    *dstWrotePtr = dst - dstStart;
    *dstCharsPtr = dst - dstStart;
    return result;
}

#ifndef WORDS_BIGENDIAN
/*
 *-------------------------------------------------------------------------
 *
 * Ucs2beToUtfProc --
 *
 *	Convert from UCS-2BE (big-endian 16-bit Unicode) to UTF-8.
 *	This is only defined on LE machines.
 *
 * Results:
 *	Returns TCL_OK if conversion was successful.
 *
 * Side effects:
 *	None.
 *
 *-------------------------------------------------------------------------
 */

static int
Ucs2beToUtfProc(
    ClientData clientData,	/* Not used. */
    CONST char *src,		/* Source string in Unicode. */
    int srcLen,			/* Source string length in bytes. */
    int flags,			/* Conversion control flags. */
    Tcl_EncodingState *statePtr,/* Place for conversion routine to store state
				 * information used during a piecewise
				 * conversion. Contents of statePtr are
				 * initialized and/or reset by conversion
				 * routine under control of flags argument. */
    char *dst,			/* Output buffer in which converted string is
				 * stored. */
    int dstLen,			/* The maximum length of output buffer in
				 * bytes. */
    int *srcReadPtr,		/* Filled with the number of bytes from the
				 * source string that were converted. This may
				 * be less than the original source length if
				 * there was a problem converting some source
				 * characters. */
    int *dstWrotePtr,		/* Filled with the number of bytes that were
				 * stored in the output buffer as a result of
				 * the conversion. */
    int *dstCharsPtr)		/* Filled with the number of characters that
				 * correspond to the bytes stored in the
				 * output buffer. */
{
    CONST char *srcStart, *srcEnd;
    char *dstEnd, *dstStart;
    int result, numChars;

    result = TCL_OK;

    /* check alignment with ucs-2 (2 == sizeof(UCS-2)) */
    if ((srcLen % 2) != 0) {
	result = TCL_CONVERT_MULTIBYTE;
	srcLen--;
    }

    srcStart = src;
    srcEnd = src + srcLen;

    dstStart = dst;
    dstEnd = dst + dstLen - TCL_UTF_MAX;

    for (numChars = 0; src < srcEnd; numChars++) {
	if (dst > dstEnd) {
	    result = TCL_CONVERT_NOSPACE;
	    break;
	}

	/*
	 * Need to swap byte-order on little-endian machines (x86) for
	 * UCS-2BE. We know this is an LE->BE swap.
	 */

	dst += Tcl_UniCharToUtf(htons(*((short *)src)), dst);
	src += 2 /* sizeof(UCS-2) */;
    }

    *srcReadPtr = src - srcStart;
    *dstWrotePtr = dst - dstStart;
    *dstCharsPtr = numChars;
    return result;
}

/*
 *-------------------------------------------------------------------------
 *
 * UtfToUcs2beProc --
 *
 *	Convert from UTF-8 to UCS-2BE (fixed 2-byte encoding).
 *
 * Results:
 *	Returns TCL_OK if conversion was successful.
 *
 * Side effects:
 *	None.
 *
 *-------------------------------------------------------------------------
 */

static int
UtfToUcs2beProc(
    ClientData clientData,	/* TableEncodingData that specifies
				 * encoding. */
    CONST char *src,		/* Source string in UTF-8. */
    int srcLen,			/* Source string length in bytes. */
    int flags,			/* Conversion control flags. */
    Tcl_EncodingState *statePtr,/* Place for conversion routine to store state
				 * information used during a piecewise
				 * conversion. Contents of statePtr are
				 * initialized and/or reset by conversion
				 * routine under control of flags argument. */
    char *dst,			/* Output buffer in which converted string is
				 * stored. */
    int dstLen,			/* The maximum length of output buffer in
				 * bytes. */
    int *srcReadPtr,		/* Filled with the number of bytes from the
				 * source string that were converted. This may
				 * be less than the original source length if
				 * there was a problem converting some source
				 * characters. */
    int *dstWrotePtr,		/* Filled with the number of bytes that were
				 * stored in the output buffer as a result of
				 * the conversion. */
    int *dstCharsPtr)		/* Filled with the number of characters that
				 * correspond to the bytes stored in the
				 * output buffer. */
{
    CONST char *srcStart, *srcEnd, *srcClose, *dstStart, *dstEnd;
    int result, numChars;
    Tcl_UniChar ch;

    srcStart = src;
    srcEnd = src + srcLen;
    srcClose = srcEnd;
    if ((flags & TCL_ENCODING_END) == 0) {
	srcClose -= TCL_UTF_MAX;
    }

    dstStart = dst;
    dstEnd = dst + dstLen - 2 /* sizeof(UCS-2) */;

    result = TCL_OK;
    for (numChars = 0; src < srcEnd; numChars++) {
	if ((src > srcClose) && (!Tcl_UtfCharComplete(src, srcEnd - src))) {
	    /*
	     * If there is more string to follow, this will ensure that the
	     * last UTF-8 character in the source buffer hasn't been cut off.
	     */

	    result = TCL_CONVERT_MULTIBYTE;
	    break;
	}
	if (dst > dstEnd) {
	    result = TCL_CONVERT_NOSPACE;
	    break;
        }
	src += Tcl_UtfToUniChar(src, &ch);

	/*
	 * Ensure big-endianness (store big bits first).
	 * XXX: This hard-codes the assumed size of Tcl_UniChar as 2. Make
	 * sure to work in char* for Tcl_UtfToUniChar alignment. [Bug 1122671]
	 */

	*dst++ = (ch >> 8);
	*dst++ = (ch & 0xFF);
    }
    *srcReadPtr = src - srcStart;
    *dstWrotePtr = dst - dstStart;
    *dstCharsPtr = numChars;
    return result;
}
#endif /* WORDS_BIGENDIAN */

/*
 *---------------------------------------------------------------------------
 *
 * TkpGetNativeFont --
 *
 *	Map a platform-specific native font name to a TkFont.
 *
 * Results:
 *	The return value is a pointer to a TkFont that represents the native
 *	font. If a native font by the given name could not be found, the
 *	return value is NULL.
 *
 *	Every call to this function returns a new TkFont structure, even if
 *	the name has already been seen before. The caller should call
 *	TkpDeleteFont() when the font is no longer needed.
 *
 *	The caller is responsible for initializing the memory associated with
 *	the generic TkFont when this function returns and releasing the
 *	contents of the generic TkFont before calling TkpDeleteFont().
 *
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

TkFont *
TkpGetNativeFont(
    Tk_Window tkwin,		/* For display where font will be used. */
    CONST char *name)		/* Platform-specific font name. */
{
    UnixFont *fontPtr;
    XFontStruct *fontStructPtr;
    FontAttributes fa;
    CONST char *p;
    int hasSpace, dashes, hasWild;

    /*
     * The behavior of X when given a name that isn't an XLFD is unspecified.
     * For example, Exceed 6 returns a valid font for any random string. This
     * is awkward since system names have higher priority than the other Tk
     * font syntaxes. So, we need to perform a quick sanity check on the name
     * and fail if it looks suspicious. We fail if the name:
     *     - contains a space immediately before a dash
     *	   - contains a space, but no '*' characters and fewer than 14 dashes
     */

    hasSpace = dashes = hasWild = 0;
    for (p = name; *p != '\0'; p++) {
	if (*p == ' ') {
	    if (p[1] == '-') {
		return NULL;
	    }
	    hasSpace = 1;
	} else if (*p == '-') {
	    dashes++;
	} else if (*p == '*') {
	    hasWild = 1;
	}
    }
    if ((dashes < 14) && !hasWild && hasSpace) {
	return NULL;
    }

    fontStructPtr = XLoadQueryFont(Tk_Display(tkwin), name);
    if (fontStructPtr == NULL) {
	/*
	 * Handle all names that look like XLFDs here. Otherwise, when
	 * TkpGetFontFromAttributes is called from generic code, any foundry
	 * or encoding information specified in the XLFD will have been parsed
	 * out and lost. But make sure we don't have an "-option value" string
	 * since TkFontParseXLFD would return a false success when attempting
	 * to parse it.
	 */

	if (name[0] == '-') {
	    if (name[1] != '*') {
		char *dash;

		dash = strchr(name + 1, '-');
		if ((dash == NULL) || (isspace(UCHAR(dash[-1])))) {
		    return NULL;
		}
	    }
	} else if (name[0] != '*') {
	    return NULL;
	}
	if (TkFontParseXLFD(name, &fa.fa, &fa.xa) != TCL_OK) {
	    return NULL;
	}
	fontStructPtr = CreateClosestFont(tkwin, &fa.fa, &fa.xa);
    }
    fontPtr = (UnixFont *) ckalloc(sizeof(UnixFont));
    InitFont(tkwin, fontStructPtr, fontPtr);

    return (TkFont *) fontPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * TkpGetFontFromAttributes --
 *
 *	Given a desired set of attributes for a font, find a font with the
 *	closest matching attributes.
 *
 * Results:
 *	The return value is a pointer to a TkFont that represents the font
 *	with the desired attributes. If a font with the desired attributes
 *	could not be constructed, some other font will be substituted
 *	automatically.
 *
 *	Every call to this function returns a new TkFont structure, even if
 *	the specified attributes have already been seen before. The caller
 *	should call TkpDeleteFont() to free the platform- specific data when
 *	the font is no longer needed.
 *
 *	The caller is responsible for initializing the memory associated with
 *	the generic TkFont when this function returns and releasing the
 *	contents of the generic TkFont before calling TkpDeleteFont().
 *
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

TkFont *
TkpGetFontFromAttributes(
    TkFont *tkFontPtr,		/* If non-NULL, store the information in this
				 * existing TkFont structure, rather than
				 * allocating a new structure to hold the
				 * font; the existing contents of the font
				 * will be released. If NULL, a new TkFont
				 * structure is allocated. */
    Tk_Window tkwin,		/* For display where font will be used. */
    CONST TkFontAttributes *faPtr)
				/* Set of attributes to match. */
{
    UnixFont *fontPtr;
    TkXLFDAttributes xa;
    XFontStruct *fontStructPtr;

    TkInitXLFDAttributes(&xa);
    fontStructPtr = CreateClosestFont(tkwin, faPtr, &xa);

    fontPtr = (UnixFont *) tkFontPtr;
    if (fontPtr == NULL) {
	fontPtr = (UnixFont *) ckalloc(sizeof(UnixFont));
    } else {
	ReleaseFont(fontPtr);
    }
    InitFont(tkwin, fontStructPtr, fontPtr);

    fontPtr->font.fa.underline = faPtr->underline;
    fontPtr->font.fa.overstrike = faPtr->overstrike;

    return (TkFont *) fontPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * TkpDeleteFont --
 *
 *	Called to release a font allocated by TkpGetNativeFont() or
 *	TkpGetFontFromAttributes(). The caller should have already released
 *	the fields of the TkFont that are used exclusively by the generic
 *	TkFont code.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	TkFont is deallocated.
 *
 *---------------------------------------------------------------------------
 */

void
TkpDeleteFont(
    TkFont *tkFontPtr)		/* Token of font to be deleted. */
{
    UnixFont *fontPtr = (UnixFont *) tkFontPtr;

    ReleaseFont(fontPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * TkpGetFontFamilies --
 *
 *	Return information about the font families that are available on the
 *	display of the given window.
 *
 * Results:
 *	Modifies interp's result object to hold a list of all the available
 *	font families.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

void
TkpGetFontFamilies(
    Tcl_Interp *interp,		/* Interp to hold result. */
    Tk_Window tkwin)		/* For display to query. */
{
    int i, new, numNames;
    char *family, **nameList;
    Tcl_HashTable familyTable;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;
    Tcl_Obj *resultPtr, *strPtr;

    resultPtr = Tcl_GetObjResult(interp);

    Tcl_InitHashTable(&familyTable, TCL_STRING_KEYS);
    nameList = ListFonts(Tk_Display(tkwin), "*", &numNames);
    for (i = 0; i < numNames; i++) {
	char *familyEnd;

	family = strchr(nameList[i] + 1, '-');
	if (family == NULL) {
	    /*
	     * Apparently, sometimes ListFonts() can return a font name with
	     * zero or one '-' character in it. This is probably indicative of
	     * a server misconfiguration, but crashing because of it is a very
	     * bad idea anyway. [Bug 1475865]
	     */

	    continue;
	}
	family++;			/* Advance to char after '-'. */
	familyEnd = strchr(family, '-');
	if (familyEnd == NULL) {
	    continue;			/* See comment above. */
	}
	*familyEnd = '\0';
	Tcl_CreateHashEntry(&familyTable, family, &new);
    }
    XFreeFontNames(nameList);

    hPtr = Tcl_FirstHashEntry(&familyTable, &search);
    while (hPtr != NULL) {
	strPtr = Tcl_NewStringObj(Tcl_GetHashKey(&familyTable, hPtr), -1);
	Tcl_ListObjAppendElement(NULL, resultPtr, strPtr);
	hPtr = Tcl_NextHashEntry(&search);
    }

    Tcl_DeleteHashTable(&familyTable);
}

/*
 *-------------------------------------------------------------------------
 *
 * TkpGetSubFonts --
 *
 *	A function used by the testing package for querying the actual screen
 *	fonts that make up a font object.
 *
 * Results:
 *	Modifies interp's result object to hold a list containing the names of
 *	the screen fonts that make up the given font object.
 *
 * Side effects:
 *	None.
 *
 *-------------------------------------------------------------------------
 */

void
TkpGetSubFonts(
    Tcl_Interp *interp,
    Tk_Font tkfont)
{
    int i;
    Tcl_Obj *objv[3], *resultPtr, *listPtr;
    UnixFont *fontPtr;
    FontFamily *familyPtr;

    resultPtr = Tcl_GetObjResult(interp);
    fontPtr = (UnixFont *) tkfont;
    for (i = 0; i < fontPtr->numSubFonts; i++) {
	familyPtr = fontPtr->subFontArray[i].familyPtr;
	objv[0] = Tcl_NewStringObj(familyPtr->faceName, -1);
	objv[1] = Tcl_NewStringObj(familyPtr->foundry, -1);
	objv[2] = Tcl_NewStringObj(
		Tcl_GetEncodingName(familyPtr->encoding), -1);
	listPtr = Tcl_NewListObj(3, objv);
	Tcl_ListObjAppendElement(NULL, resultPtr, listPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkpGetFontAttrsForChar --
 *
 *	Retrieve the font attributes of the actual font used to render a given
 *	character.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The font attributes are stored in *faPtr.
 *
 *----------------------------------------------------------------------
 */

void
TkpGetFontAttrsForChar(
    Tk_Window tkwin,		/* Window on the font's display */
    Tk_Font tkfont,		/* Font to query */
    Tcl_UniChar c,		/* Character of interest */
    TkFontAttributes *faPtr)	/* Output: Font attributes */
{
    FontAttributes atts;
    UnixFont *fontPtr = (UnixFont *) tkfont;
				/* Structure describing the logical font */
    SubFont *lastSubFontPtr = &fontPtr->subFontArray[0];
				/* Pointer to subfont array in case
				 * FindSubFontForChar needs to fix up the
				 * memory allocation */
    SubFont *thisSubFontPtr = FindSubFontForChar(fontPtr, c, &lastSubFontPtr);
				/* Pointer to the subfont to use for the given
				 * character */
    GetFontAttributes(Tk_Display(tkwin), thisSubFontPtr->fontStructPtr, &atts);
    *faPtr = atts.fa;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_MeasureChars --
 *
 *	Determine the number of characters from the string that will fit in
 *	the given horizontal span. The measurement is done under the
 *	assumption that Tk_DrawChars() will be used to actually display the
 *	characters.
 *
 * Results:
 *	The return value is the number of bytes from source that fit into the
 *	span that extends from 0 to maxLength. *lengthPtr is filled with the
 *	x-coordinate of the right edge of the last character that did fit.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

int
Tk_MeasureChars(
    Tk_Font tkfont,		/* Font in which characters will be drawn. */
    CONST char *source,		/* UTF-8 string to be displayed. Need not be
				 * '\0' terminated. */
    int numBytes,		/* Maximum number of bytes to consider from
				 * source string. */
    int maxLength,		/* If >= 0, maxLength specifies the longest
				 * permissible line length in pixels; don't
				 * consider any character that would cross
				 * this x-position. If < 0, then line length
				 * is unbounded and the flags argument is
				 * ignored. */
    int flags,			/* Various flag bits OR-ed together:
				 * TK_PARTIAL_OK means include the last char
				 * which only partially fit on this line.
				 * TK_WHOLE_WORDS means stop on a word
				 * boundary, if possible. TK_AT_LEAST_ONE
				 * means return at least one character even if
				 * no characters fit. */
    int *lengthPtr)		/* Filled with x-location just after the
				 * terminating character. */
{
    UnixFont *fontPtr;
    SubFont *lastSubFontPtr;
    int curX, curByte;

    /*
     * Unix does not use kerning or fractional character widths when
     * displaying text on the screen. So that means we can safely measure
     * individual characters or spans of characters and add up the widths w/o
     * any "off-by-one-pixel" errors.
     */

    fontPtr = (UnixFont *) tkfont;

    lastSubFontPtr = &fontPtr->subFontArray[0];

    if (numBytes == 0) {
	curX = 0;
	curByte = 0;
    } else if (maxLength < 0) {
	CONST char *p, *end, *next;
	Tcl_UniChar ch;
	SubFont *thisSubFontPtr;
	FontFamily *familyPtr;
	Tcl_DString runString;

	/*
	 * A three step process:
	 * 1. Find a contiguous range of characters that can all be
	 *    represented by a single screen font.
	 * 2. Convert those chars to the encoding of that font.
	 * 3. Measure converted chars.
	 */

	curX = 0;
	end = source + numBytes;
	for (p = source; p < end; ) {
	    next = p + Tcl_UtfToUniChar(p, &ch);
	    thisSubFontPtr = FindSubFontForChar(fontPtr, ch, &lastSubFontPtr);
	    if (thisSubFontPtr != lastSubFontPtr) {
		familyPtr = lastSubFontPtr->familyPtr;
		Tcl_UtfToExternalDString(familyPtr->encoding, source,
			p - source, &runString);
		if (familyPtr->isTwoByteFont) {
		    curX += XTextWidth16(lastSubFontPtr->fontStructPtr,
			    (XChar2b *) Tcl_DStringValue(&runString),
			    Tcl_DStringLength(&runString) / 2);
		} else {
		    curX += XTextWidth(lastSubFontPtr->fontStructPtr,
			    Tcl_DStringValue(&runString),
			    Tcl_DStringLength(&runString));
		}
		Tcl_DStringFree(&runString);
		lastSubFontPtr = thisSubFontPtr;
		source = p;
	    }
	    p = next;
	}
	familyPtr = lastSubFontPtr->familyPtr;
	Tcl_UtfToExternalDString(familyPtr->encoding, source, p - source,
		&runString);
	if (familyPtr->isTwoByteFont) {
	    curX += XTextWidth16(lastSubFontPtr->fontStructPtr,
		    (XChar2b *) Tcl_DStringValue(&runString),
		    Tcl_DStringLength(&runString) >> 1);
	} else {
	    curX += XTextWidth(lastSubFontPtr->fontStructPtr,
		    Tcl_DStringValue(&runString),
		    Tcl_DStringLength(&runString));
	}
	Tcl_DStringFree(&runString);
	curByte = numBytes;
    } else {
	CONST char *p, *end, *next, *term;
	int newX, termX, sawNonSpace, dstWrote;
	Tcl_UniChar ch;
	FontFamily *familyPtr;
	char buf[16];

	/*
	 * How many chars will fit in the space allotted? This first version
	 * may be inefficient because it measures every character
	 * individually.
	 */

	next = source + Tcl_UtfToUniChar(source, &ch);
	newX = curX = termX = 0;

	term = source;
	end = source + numBytes;

	sawNonSpace = (ch > 255) || !isspace(ch);
	familyPtr = lastSubFontPtr->familyPtr;
	for (p = source; ; ) {
	    if ((ch < BASE_CHARS) && (fontPtr->widths[ch] != 0)) {
		newX += fontPtr->widths[ch];
	    } else {
		lastSubFontPtr = FindSubFontForChar(fontPtr, ch, NULL);
		familyPtr = lastSubFontPtr->familyPtr;
		Tcl_UtfToExternal(NULL, familyPtr->encoding, p, next - p,
			0, NULL, buf, sizeof(buf), NULL, &dstWrote, NULL);
		if (familyPtr->isTwoByteFont) {
		    newX += XTextWidth16(lastSubFontPtr->fontStructPtr,
			    (XChar2b *) buf, dstWrote >> 1);
		} else {
		    newX += XTextWidth(lastSubFontPtr->fontStructPtr, buf,
			    dstWrote);
		}
	    }
	    if (newX > maxLength) {
		break;
	    }
	    curX = newX;
	    p = next;
	    if (p >= end) {
		term = end;
		termX = curX;
		break;
	    }

	    next += Tcl_UtfToUniChar(next, &ch);
	    if ((ch < 256) && isspace(ch)) {
		if (sawNonSpace) {
		    term = p;
		    termX = curX;
		    sawNonSpace = 0;
		}
	    } else {
		sawNonSpace = 1;
	    }
	}

	/*
	 * P points to the first character that doesn't fit in the desired
	 * span. Use the flags to figure out what to return.
	 */

	if ((flags & TK_PARTIAL_OK) && (p < end) && (curX < maxLength)) {
	    /*
	     * Include the first character that didn't quite fit in the
	     * desired span. The width returned will include the width of that
	     * extra character.
	     */

	    curX = newX;
	    p += Tcl_UtfToUniChar(p, &ch);
	}
	if ((flags & TK_AT_LEAST_ONE) && (term == source) && (p < end)) {
	    term = p;
	    termX = curX;
	    if (term == source) {
		term += Tcl_UtfToUniChar(term, &ch);
		termX = newX;
	    }
	} else if ((p >= end) || !(flags & TK_WHOLE_WORDS)) {
	    term = p;
	    termX = curX;
	}

	curX = termX;
	curByte = term - source;
    }

    *lengthPtr = curX;
    return curByte;
}

/*
 *---------------------------------------------------------------------------
 *
 * TkpMeasureCharsInContext --
 *
 *	Determine the number of bytes from the string that will fit in the
 *	given horizontal span. The measurement is done under the assumption
 *	that TkpDrawCharsInContext() will be used to actually display the
 *	characters.
 *
 *	This one is almost the same as Tk_MeasureChars(), but with access to
 *	all the characters on the line for context. On X11 this context isn't
 *	consulted, so we just call Tk_MeasureChars().
 *
 * Results:
 *	The return value is the number of bytes from source that fit into the
 *	span that extends from 0 to maxLength. *lengthPtr is filled with the
 *	x-coordinate of the right edge of the last character that did fit.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

int
TkpMeasureCharsInContext(
    Tk_Font tkfont,		/* Font in which characters will be drawn. */
    CONST char *source,		/* UTF-8 string to be displayed. Need not be
				 * '\0' terminated. */
    int numBytes,		/* Maximum number of bytes to consider from
				 * source string in all. */
    int rangeStart,		/* Index of first byte to measure. */
    int rangeLength,		/* Length of range to measure in bytes. */
    int maxLength,		/* If >= 0, maxLength specifies the longest
				 * permissible line length; don't consider any
				 * character that would cross this x-position.
				 * If < 0, then line length is unbounded and
				 * the flags argument is ignored. */
    int flags,			/* Various flag bits OR-ed together:
				 * TK_PARTIAL_OK means include the last char
				 * which only partially fit on this line.
				 * TK_WHOLE_WORDS means stop on a word
				 * boundary, if possible. TK_AT_LEAST_ONE
				 * means return at least one character even if
				 * no characters fit. TK_ISOLATE_END means
				 * that the last character should not be
				 * considered in context with the rest of the
				 * string (used for breaking lines). */
    int *lengthPtr)		/* Filled with x-location just after the
				 * terminating character. */
{
    (void) numBytes; /*unused*/
    return Tk_MeasureChars(tkfont, source + rangeStart, rangeLength,
	    maxLength, flags, lengthPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_DrawChars --
 *
 *	Draw a string of characters on the screen. Tk_DrawChars() expands
 *	control characters that occur in the string to \xNN sequences.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information gets drawn on the screen.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_DrawChars(
    Display *display,		/* Display on which to draw. */
    Drawable drawable,		/* Window or pixmap in which to draw. */
    GC gc,			/* Graphics context for drawing characters. */
    Tk_Font tkfont,		/* Font in which characters will be drawn;
				 * must be the same as font used in GC. */
    CONST char *source,		/* UTF-8 string to be displayed. Need not be
				 * '\0' terminated. All Tk meta-characters
				 * (tabs, control characters, and newlines)
				 * should be stripped out of the string that
				 * is passed to this function. If they are not
				 * stripped out, they will be displayed as
				 * regular printing characters. */
    int numBytes,		/* Number of bytes in string. */
    int x, int y)		/* Coordinates at which to place origin of
				 * string when drawing. */
{
    UnixFont *fontPtr;
    SubFont *thisSubFontPtr, *lastSubFontPtr;
    Tcl_DString runString;
    CONST char *p, *end, *next;
    int xStart, needWidth, window_width, do_width;
    Tcl_UniChar ch;
    FontFamily *familyPtr;
#ifdef TK_DRAW_CHAR_XWINDOW_CHECK
    int rx, ry;
    unsigned int width, height, border_width, depth;
    Drawable root;
#endif

    fontPtr = (UnixFont *) tkfont;
    lastSubFontPtr = &fontPtr->subFontArray[0];

    xStart = x;

#ifdef TK_DRAW_CHAR_XWINDOW_CHECK
    /*
     * Get the window width so we can abort drawing outside of the window
     */

    if (XGetGeometry(display, drawable, &root, &rx, &ry, &width, &height,
	    &border_width, &depth) == False) {
	window_width = INT_MAX;
    } else {
	window_width = width;
    }
#else
    /*
     * This is used by default until we find a solution that doesn't do a
     * round-trip to the X server (needed to get Tk cached window width).
     */

    window_width = 32768;
#endif

    end = source + numBytes;
    needWidth = fontPtr->font.fa.underline + fontPtr->font.fa.overstrike;
    for (p = source; p <= end; ) {
	if (p < end) {
	    next = p + Tcl_UtfToUniChar(p, &ch);
	    thisSubFontPtr = FindSubFontForChar(fontPtr, ch, &lastSubFontPtr);
	} else {
	    next = p + 1;
	    thisSubFontPtr = lastSubFontPtr;
	}
	if ((thisSubFontPtr != lastSubFontPtr)
		|| (p == end) || (p-source > 200)) {
	    if (p > source) {
		do_width = (needWidth || (p != end)) ? 1 : 0;
		familyPtr = lastSubFontPtr->familyPtr;

		Tcl_UtfToExternalDString(familyPtr->encoding, source,
			p - source, &runString);
		if (familyPtr->isTwoByteFont) {
		    XDrawString16(display, drawable, gc, x, y,
			    (XChar2b *) Tcl_DStringValue(&runString),
			    Tcl_DStringLength(&runString) / 2);
		    if (do_width) {
			x += XTextWidth16(lastSubFontPtr->fontStructPtr,
				(XChar2b *) Tcl_DStringValue(&runString),
				Tcl_DStringLength(&runString) / 2);
		    }
		} else {
		    XDrawString(display, drawable, gc, x, y,
			    Tcl_DStringValue(&runString),
			    Tcl_DStringLength(&runString));
		    if (do_width) {
			x += XTextWidth(lastSubFontPtr->fontStructPtr,
				Tcl_DStringValue(&runString),
				Tcl_DStringLength(&runString));
		    }
		}
		Tcl_DStringFree(&runString);
	    }
	    lastSubFontPtr = thisSubFontPtr;
	    source = p;
	    XSetFont(display, gc, lastSubFontPtr->fontStructPtr->fid);
	    if (x > window_width) {
		break;
	    }
	}
	p = next;
    }

    if (lastSubFontPtr != &fontPtr->subFontArray[0]) {
	XSetFont(display, gc, fontPtr->subFontArray[0].fontStructPtr->fid);
    }

    if (fontPtr->font.fa.underline != 0) {
	XFillRectangle(display, drawable, gc, xStart,
		y + fontPtr->underlinePos,
		(unsigned) (x - xStart), (unsigned) fontPtr->barHeight);
    }
    if (fontPtr->font.fa.overstrike != 0) {
	y -= fontPtr->font.fm.descent + (fontPtr->font.fm.ascent) / 10;
	XFillRectangle(display, drawable, gc, xStart, y,
		(unsigned) (x - xStart), (unsigned) fontPtr->barHeight);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * TkpDrawCharsInContext --
 *
 *	Draw a string of characters on the screen like Tk_DrawChars(), but
 *	with access to all the characters on the line for context. On X11 this
 *	context isn't consulted, so we just call Tk_DrawChars().
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information gets drawn on the screen.
 *
 *---------------------------------------------------------------------------
 */

void
TkpDrawCharsInContext(
    Display *display,		/* Display on which to draw. */
    Drawable drawable,		/* Window or pixmap in which to draw. */
    GC gc,			/* Graphics context for drawing characters. */
    Tk_Font tkfont,		/* Font in which characters will be drawn;
				 * must be the same as font used in GC. */
    CONST char *source,		/* UTF-8 string to be displayed. Need not be
				 * '\0' terminated. All Tk meta-characters
				 * (tabs, control characters, and newlines)
				 * should be stripped out of the string that
				 * is passed to this function. If they are not
				 * stripped out, they will be displayed as
				 * regular printing characters. */
    int numBytes,		/* Number of bytes in string. */
    int rangeStart,		/* Index of first byte to draw. */
    int rangeLength,		/* Length of range to draw in bytes. */
    int x, int y)		/* Coordinates at which to place origin of the
				 * whole (not just the range) string when
				 * drawing. */
{
    (void) numBytes; /*unused*/

    Tk_DrawChars(display, drawable, gc, tkfont, source + rangeStart,
	    rangeLength, x, y);
}

/*
 *-------------------------------------------------------------------------
 *
 * CreateClosestFont --
 *
 *	Helper for TkpGetNativeFont() and TkpGetFontFromAttributes(). Given a
 *	set of font attributes, construct a close XFontStruct. If requested
 *	face name is not available, automatically substitutes an alias for
 *	requested face name. If encoding is not specified (or the requested
 *	one is not available), automatically chooses another encoding from the
 *	list of preferred encodings. If the foundry is not specified (or is
 *	not available) automatically prefers "adobe" foundry. For all other
 *	attributes, if the requested value was not available, the appropriate
 *	"close" value will be used.
 *
 * Results:
 *	Return value is the XFontStruct that best matched the requested
 *	attributes. The return value is never NULL; some font will always be
 *	returned.
 *
 * Side effects:
 *	None.
 *
 *-------------------------------------------------------------------------
 */

static XFontStruct *
CreateClosestFont(
    Tk_Window tkwin,		/* For display where font will be used. */
    CONST TkFontAttributes *faPtr,
				/* Set of generic attributes to match. */
    CONST TkXLFDAttributes *xaPtr)
				/* Set of X-specific attributes to match. */
{
    FontAttributes want;
    char **nameList;
    int numNames, nameIdx, bestIdx[2];
    Display *display;
    XFontStruct *fontStructPtr;
    unsigned int bestScore[2];

    want.fa = *faPtr;
    want.xa = *xaPtr;

    if (want.xa.foundry == NULL) {
	want.xa.foundry = Tk_GetUid("adobe");
    }
    if (want.fa.family == NULL) {
	want.fa.family = Tk_GetUid("fixed");
    }
    want.fa.size = -TkFontGetPixels(tkwin, faPtr->size);
    if (want.xa.charset == NULL || *want.xa.charset == '\0') {
	want.xa.charset = Tk_GetUid("iso8859-1");	/* locale. */
    }

    display = Tk_Display(tkwin);

    /*
     * Algorithm to get the closest font to the name requested.
     *
     * try fontname
     * try all aliases for fontname
     * foreach fallback for fontname
     *	    try the fallback
     *	    try all aliases for the fallback
     */

    nameList = ListFontOrAlias(display, want.fa.family, &numNames);
    if (numNames == 0) {
	char ***fontFallbacks;
	int i, j;
	char *fallback;

	fontFallbacks = TkFontGetFallbacks();
	for (i = 0; fontFallbacks[i] != NULL; i++) {
	    for (j = 0; (fallback = fontFallbacks[i][j]) != NULL; j++) {
		if (strcasecmp(want.fa.family, fallback) == 0) {
		    break;
		}
	    }
	    if (fallback != NULL) {
		for (j = 0; (fallback = fontFallbacks[i][j]) != NULL; j++) {
		    nameList = ListFontOrAlias(display, fallback, &numNames);
		    if (numNames != 0) {
			goto found;
		    }
		}
	    }
	}
	nameList = ListFonts(display, "fixed", &numNames);
	if (numNames == 0) {
	    nameList = ListFonts(display, "*", &numNames);
	}
	if (numNames == 0) {
	    return GetSystemFont(display);
	}
    }

  found:
    bestIdx[0] = -1;
    bestIdx[1] = -1;
    bestScore[0] = (unsigned int) -1;
    bestScore[1] = (unsigned int) -1;
    for (nameIdx = 0; nameIdx < numNames; nameIdx++) {
	FontAttributes got;
	int scalable;
	unsigned int score;

	if (TkFontParseXLFD(nameList[nameIdx], &got.fa, &got.xa) != TCL_OK) {
	    continue;
	}
	IdentifySymbolEncodings(&got);
	scalable = (got.fa.size == 0);
	score = RankAttributes(&want, &got);
	if (score < bestScore[scalable]) {
	    bestIdx[scalable] = nameIdx;
	    bestScore[scalable] = score;
	}
	if (score == 0) {
	    break;
	}
    }

    fontStructPtr = GetScreenFont(display, &want, nameList, bestIdx,
	    bestScore);
    XFreeFontNames(nameList);

    if (fontStructPtr == NULL) {
	return GetSystemFont(display);
    }
    return fontStructPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * InitFont --
 *
 *	Helper for TkpGetNativeFont() and TkpGetFontFromAttributes().
 *	Initializes the memory for a new UnixFont that wraps the
 *	platform-specific data.
 *
 *	The caller is responsible for initializing the fields of the TkFont
 *	that are used exclusively by the generic TkFont code, and for
 *	releasing those fields before calling TkpDeleteFont().
 *
 * Results:
 *	Fills the WinFont structure.
 *
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

static void
InitFont(
    Tk_Window tkwin,		/* For screen where font will be used. */
    XFontStruct *fontStructPtr,	/* X information about font. */
    UnixFont *fontPtr)		/* Filled with information constructed from
				 * the above arguments. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    unsigned long value;
    int minHi, maxHi, minLo, maxLo, fixed, width, limit, i, n;
    FontAttributes fa;
    TkFontAttributes *faPtr;
    TkFontMetrics *fmPtr;
    SubFont *controlPtr, *subFontPtr;
    char *pageMap;
    Display *display;

    /*
     * Get all font attributes and metrics.
     */

    display = Tk_Display(tkwin);
    GetFontAttributes(display, fontStructPtr, &fa);

    minHi = fontStructPtr->min_byte1;
    maxHi = fontStructPtr->max_byte1;
    minLo = fontStructPtr->min_char_or_byte2;
    maxLo = fontStructPtr->max_char_or_byte2;

    fixed = 1;
    if (fontStructPtr->per_char != NULL) {
	width = 0;
	limit = (maxHi - minHi + 1) * (maxLo - minLo + 1);
	for (i = 0; i < limit; i++) {
	    n = fontStructPtr->per_char[i].width;
	    if (n != 0) {
		if (width == 0) {
		    width = n;
		} else if (width != n) {
		    fixed = 0;
		    break;
		}
	    }
	}
    }

    fontPtr->font.fid = fontStructPtr->fid;

    faPtr = &fontPtr->font.fa;
    faPtr->family = fa.fa.family;
    faPtr->size = TkFontGetPoints(tkwin, fa.fa.size);
    faPtr->weight = fa.fa.weight;
    faPtr->slant = fa.fa.slant;
    faPtr->underline = 0;
    faPtr->overstrike = 0;

    fmPtr = &fontPtr->font.fm;
    fmPtr->ascent = fontStructPtr->ascent;
    fmPtr->descent = fontStructPtr->descent;
    fmPtr->maxWidth = fontStructPtr->max_bounds.width;
    fmPtr->fixed = fixed;

    fontPtr->display = display;
    fontPtr->pixelSize = TkFontGetPixels(tkwin, fa.fa.size);
    fontPtr->xa = fa.xa;

    fontPtr->numSubFonts = 1;
    fontPtr->subFontArray = fontPtr->staticSubFonts;
    InitSubFont(display, fontStructPtr, 1, &fontPtr->subFontArray[0]);

    fontPtr->controlSubFont = fontPtr->subFontArray[0];
    subFontPtr = FindSubFontForChar(fontPtr, '0', NULL);
    controlPtr = &fontPtr->controlSubFont;
    controlPtr->fontStructPtr = subFontPtr->fontStructPtr;
    controlPtr->familyPtr = &tsdPtr->controlFamily;
    controlPtr->fontMap = tsdPtr->controlFamily.fontMap;

    pageMap = fontPtr->subFontArray[0].fontMap[0];
    for (i = 0; i < 256; i++) {
	if ((minHi > 0) || (i < minLo) || (i > maxLo)
		|| (((pageMap[i>>3] >> (i&7)) & 1) == 0)) {
	    n = 0;
	} else if (fontStructPtr->per_char == NULL) {
	    n = fontStructPtr->max_bounds.width;
	} else {
	    n = fontStructPtr->per_char[i - minLo].width;
	}
	fontPtr->widths[i] = n;
    }

    if (XGetFontProperty(fontStructPtr, XA_UNDERLINE_POSITION, &value)) {
	fontPtr->underlinePos = value;
    } else {
	/*
	 * If the XA_UNDERLINE_POSITION property does not exist, the X manual
	 * recommends using the following value:
	 */

	fontPtr->underlinePos = fontStructPtr->descent / 2;
    }
    fontPtr->barHeight = 0;
    if (XGetFontProperty(fontStructPtr, XA_UNDERLINE_THICKNESS, &value)) {
	fontPtr->barHeight = value;
    }
    if (fontPtr->barHeight == 0) {
	/*
	 * If the XA_UNDERLINE_THICKNESS property does not exist, the X manual
	 * recommends using the width of the stem on a capital letter. I don't
	 * know of a way to get the stem width of a letter, so guess and use
	 * 1/3 the width of a capital I.
	 */

	fontPtr->barHeight = fontPtr->widths['I'] / 3;
	if (fontPtr->barHeight == 0) {
	    fontPtr->barHeight = 1;
	}
    }
    if (fontPtr->underlinePos + fontPtr->barHeight > fontStructPtr->descent) {
	/*
	 * If this set of cobbled together 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->barHeight = fontStructPtr->descent - fontPtr->underlinePos;
	if (fontPtr->barHeight == 0) {
	    fontPtr->underlinePos--;
	    fontPtr->barHeight = 1;
	}
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * ReleaseFont --
 *
 *	Called to release the unix-specific contents of a TkFont. The caller
 *	is responsible for freeing the memory used by the font itself.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is freed.
 *
 *---------------------------------------------------------------------------
 */

static void
ReleaseFont(
    UnixFont *fontPtr)		/* The font to delete. */
{
    int i;

    for (i = 0; i < fontPtr->numSubFonts; i++) {
	ReleaseSubFont(fontPtr->display, &fontPtr->subFontArray[i]);
    }
    if (fontPtr->subFontArray != fontPtr->staticSubFonts) {
	ckfree((char *) fontPtr->subFontArray);
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * InitSubFont --
 *
 *	Wrap a screen font and load the FontFamily that represents it. Used to
 *	prepare a SubFont so that characters can be mapped from UTF-8 to the
 *	charset of the font.
 *
 * Results:
 *	The subFontPtr is filled with information about the font.
 *
 * Side effects:
 *	None.
 *
 *-------------------------------------------------------------------------
 */

static void
InitSubFont(
    Display *display,		/* Display in which font will be used. */
    XFontStruct *fontStructPtr,	/* The screen font. */
    int base,			/* Non-zero if this SubFont is being used as
				 * the base font for a font object. */
    SubFont *subFontPtr)	/* Filled with SubFont constructed from above
				 * attributes. */
{
    subFontPtr->fontStructPtr = fontStructPtr;
    subFontPtr->familyPtr = AllocFontFamily(display, fontStructPtr, base);
    subFontPtr->fontMap = subFontPtr->familyPtr->fontMap;
}

/*
 *-------------------------------------------------------------------------
 *
 * ReleaseSubFont --
 *
 *	Called to release the contents of a SubFont. The caller is responsible
 *	for freeing the memory used by the SubFont itself.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory and resources are freed.
 *
 *---------------------------------------------------------------------------
 */

static void
ReleaseSubFont(
    Display *display,		/* Display which owns screen font. */
    SubFont *subFontPtr)	/* The SubFont to delete. */
{
    XFreeFont(display, subFontPtr->fontStructPtr);
    FreeFontFamily(subFontPtr->familyPtr);
}

/*
 *-------------------------------------------------------------------------
 *
 * AllocFontFamily --
 *
 *	Find the FontFamily structure associated with the given font name.
 *	The information should be stored by the caller in a SubFont and used
 *	when determining if that SubFont supports a character.
 *
 *	Cannot use the string name used to construct the font as the key,
 *	because the capitalization may not be canonical. Therefore use the
 *	face name actually retrieved from the font metrics as the key.
 *
 * Results:
 *	A pointer to a FontFamily. The reference count in the FontFamily is
 *	automatically incremented. When the SubFont is released, the reference
 *	count is decremented. When no SubFont is using this FontFamily, it may
 *	be deleted.
 *
 * Side effects:
 *	A new FontFamily structure will be allocated if this font family has
 *	not been seen. TrueType character existence metrics are loaded into
 *	the FontFamily structure.
 *
 *-------------------------------------------------------------------------
 */

static FontFamily *
AllocFontFamily(
    Display *display,		/* Display in which font will be used. */
    XFontStruct *fontStructPtr,	/* Screen font whose FontFamily is to be
				 * returned. */
    int base)			/* Non-zero if this font family is to be used
				 * in the base font of a font object. */
{
    FontFamily *familyPtr;
    FontAttributes fa;
    Tcl_Encoding encoding;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    GetFontAttributes(display, fontStructPtr, &fa);
    encoding = Tcl_GetEncoding(NULL, GetEncodingAlias(fa.xa.charset));

    familyPtr = tsdPtr->fontFamilyList;
    for (; familyPtr != NULL; familyPtr = familyPtr->nextPtr) {
	if ((familyPtr->faceName == fa.fa.family)
		&& (familyPtr->foundry == fa.xa.foundry)
		&& (familyPtr->encoding == encoding)) {
	    Tcl_FreeEncoding(encoding);
	    familyPtr->refCount++;
	    return familyPtr;
	}
    }

    familyPtr = (FontFamily *) ckalloc(sizeof(FontFamily));
    memset(familyPtr, 0, sizeof(FontFamily));
    familyPtr->nextPtr = tsdPtr->fontFamilyList;
    tsdPtr->fontFamilyList = familyPtr;

    /*
     * Set key for this FontFamily.
     */

    familyPtr->foundry = fa.xa.foundry;
    familyPtr->faceName = fa.fa.family;
    familyPtr->encoding = encoding;

    /*
     * An initial refCount of 2 means that FontFamily information will persist
     * even when the SubFont that loaded the FontFamily is released. Change it
     * to 1 to cause FontFamilies to be unloaded when not in use.
     */

    familyPtr->refCount = 2;

    /*
     * One byte/character fonts have both min_byte1 and max_byte1 0, and
     * max_char_or_byte2 <= 255. Anything else specifies a two byte/character
     * font.
     */

    familyPtr->isTwoByteFont = !(
	    (fontStructPtr->min_byte1 == 0) &&
	    (fontStructPtr->max_byte1 == 0) &&
	    (fontStructPtr->max_char_or_byte2 < 256));
    return familyPtr;
}

/*
 *-------------------------------------------------------------------------
 *
 * FreeFontFamily --
 *
 *	Called to free an FontFamily when the SubFont is finished using it.
 *	Frees the contents of the FontFamily and the memory used by the
 *	FontFamily itself.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *-------------------------------------------------------------------------
 */

static void
FreeFontFamily(
    FontFamily *familyPtr)	/* The FontFamily to delete. */
{
    FontFamily **familyPtrPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    int i;

    if (familyPtr == NULL) {
	return;
    }
    familyPtr->refCount--;
    if (familyPtr->refCount > 0) {
	return;
    }
    Tcl_FreeEncoding(familyPtr->encoding);
    for (i = 0; i < FONTMAP_PAGES; i++) {
	if (familyPtr->fontMap[i] != NULL) {
	    ckfree(familyPtr->fontMap[i]);
	}
    }

    /*
     * Delete from list.
     */

    for (familyPtrPtr = &tsdPtr->fontFamilyList; ; ) {
	if (*familyPtrPtr == familyPtr) {
	    *familyPtrPtr = familyPtr->nextPtr;
	    break;
	}
	familyPtrPtr = &(*familyPtrPtr)->nextPtr;
    }

    ckfree((char *) familyPtr);
}

/*
 *-------------------------------------------------------------------------
 *
 * FindSubFontForChar --
 *
 *	Determine which screen font is necessary to use to display the given
 *	character. If the font object does not have a screen font that can
 *	display the character, another screen font may be loaded into the font
 *	object, following a set of preferred fallback rules.
 *
 * Results:
 *	The return value is the SubFont to use to display the given character.
 *
 * Side effects:
 *	The contents of fontPtr are modified to cache the results of the
 *	lookup and remember any SubFonts that were dynamically loaded. The
 *	table of SubFonts might be extended, and if a non-NULL reference to a
 *	subfont pointer is available, it is updated if it previously pointed
 *	into the old subfont table.
 *
 *-------------------------------------------------------------------------
 */

static SubFont *
FindSubFontForChar(
    UnixFont *fontPtr,		/* The font object with which the character
				 * will be displayed. */
    int ch,			/* The Unicode character to be displayed. */
    SubFont **fixSubFontPtrPtr)	/* Subfont reference to fix up if we
				 * reallocate our subfont table. */
{
    int i, j, k, numNames;
    Tk_Uid faceName;
    char *fallback, **aliases, **nameList, **anyFallbacks, ***fontFallbacks;
    SubFont *subFontPtr;
    Tcl_DString ds;

    if (FontMapLookup(&fontPtr->subFontArray[0], ch)) {
	return &fontPtr->subFontArray[0];
    }

    for (i = 1; i < fontPtr->numSubFonts; i++) {
	if (FontMapLookup(&fontPtr->subFontArray[i], ch)) {
	    return &fontPtr->subFontArray[i];
	}
    }

    if (FontMapLookup(&fontPtr->controlSubFont, ch)) {
	return &fontPtr->controlSubFont;
    }

    /*
     * Keep track of all face names that we check, so we don't check some name
     * multiple times if it can be reached by multiple paths.
     */

    Tcl_DStringInit(&ds);

    /*
     * Are there any other fonts with the same face name as the base font that
     * could display this character, e.g., if the base font is
     * adobe:fixed:iso8859-1, we could might be able to use
     * misc:fixed:iso8859-8 or sony:fixed:jisx0208.1983-0
     */

    faceName = fontPtr->font.fa.family;
    if (SeenName(faceName, &ds) == 0) {
	subFontPtr = CanUseFallback(fontPtr, faceName, ch, fixSubFontPtrPtr);
	if (subFontPtr != NULL) {
	    goto end;
	}
    }

    aliases = TkFontGetAliasList(faceName);

    subFontPtr = NULL;
    fontFallbacks = TkFontGetFallbacks();
    for (i = 0; fontFallbacks[i] != NULL; i++) {
	for (j = 0; (fallback = fontFallbacks[i][j]) != NULL; j++) {
	    if (strcasecmp(fallback, faceName) == 0) {
		/*
		 * If the base font has a fallback...
		 */

		goto tryfallbacks;
	    } else if (aliases != NULL) {
		/*
		 * Or if an alias for the base font has a fallback...
		 */

		for (k = 0; aliases[k] != NULL; k++) {
		    if (strcasecmp(fallback, aliases[k]) == 0) {
			goto tryfallbacks;
		    }
		}
	    }
	}
	continue;

    tryfallbacks:

	/*
	 * ...then see if we can use one of the fallbacks, or an alias for one
	 * of the fallbacks.
	 */

	for (j = 0; (fallback = fontFallbacks[i][j]) != NULL; j++) {
	    subFontPtr = CanUseFallbackWithAliases(fontPtr, fallback, ch, &ds,
		    fixSubFontPtrPtr);
	    if (subFontPtr != NULL) {
		goto end;
	    }
	}
    }

    /*
     * See if we can use something from the global fallback list.
     */

    anyFallbacks = TkFontGetGlobalClass();
    for (i = 0; (fallback = anyFallbacks[i]) != NULL; i++) {
	subFontPtr = CanUseFallbackWithAliases(fontPtr, fallback, ch, &ds,
		fixSubFontPtrPtr);
	if (subFontPtr != NULL) {
	    goto end;
	}
    }

    /*
     * Try all face names available in the whole system until we find one that
     * can be used.
     */

    nameList = ListFonts(fontPtr->display, "*", &numNames);
    for (i = 0; i < numNames; i++) {
	fallback = strchr(nameList[i] + 1, '-') + 1;
	strchr(fallback, '-')[0] = '\0';
	if (SeenName(fallback, &ds) == 0) {
	    subFontPtr = CanUseFallback(fontPtr, fallback, ch,
		    fixSubFontPtrPtr);
	    if (subFontPtr != NULL) {
		XFreeFontNames(nameList);
		goto end;
	    }
	}
    }
    XFreeFontNames(nameList);

  end:
    Tcl_DStringFree(&ds);

    if (subFontPtr == NULL) {
	/*
	 * No font can display this character, so it will be displayed as a
	 * control character expansion.
	 */

	subFontPtr = &fontPtr->controlSubFont;
	FontMapInsert(subFontPtr, ch);
    }
    return subFontPtr;
}

/*
 *-------------------------------------------------------------------------
 *
 * FontMapLookup --
 *
 *	See if the screen font can display the given character.
 *
 * Results:
 *	The return value is 0 if the screen font cannot display the character,
 *	non-zero otherwise.
 *
 * Side effects:
 *	New pages are added to the font mapping cache whenever the character
 *	belongs to a page that hasn't been seen before. When a page is loaded,
 *	information about all the characters on that page is stored, not just
 *	for the single character in question.
 *
 *-------------------------------------------------------------------------
 */

static int
FontMapLookup(
    SubFont *subFontPtr,	/* Contains font mapping cache to be queried
				 * and possibly updated. */
    int ch)			/* Character to be tested. */
{
    int row, bitOffset;

    row = ch >> FONTMAP_SHIFT;
    if (subFontPtr->fontMap[row] == NULL) {
	FontMapLoadPage(subFontPtr, row);
    }
    bitOffset = ch & (FONTMAP_BITSPERPAGE - 1);
    return (subFontPtr->fontMap[row][bitOffset >> 3] >> (bitOffset & 7)) & 1;
}

/*
 *-------------------------------------------------------------------------
 *
 * FontMapInsert --
 *
 *	Tell the font mapping cache that the given screen font should be used
 *	to display the specified character. This is called when no font on the
 *	system can be be found that can display that character; we lie to the
 *	font and tell it that it can display the character, otherwise we would
 *	end up re-searching the entire fallback hierarchy every time that
 *	character was seen.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	New pages are added to the font mapping cache whenever the character
 *	belongs to a page that hasn't been seen before. When a page is loaded,
 *	information about all the characters on that page is stored, not just
 *	for the single character in question.
 *
 *-------------------------------------------------------------------------
 */

static void
FontMapInsert(
    SubFont *subFontPtr,	/* Contains font mapping cache to be
				 * updated. */
    int ch)			/* Character to be added to cache. */
{
    int row, bitOffset;

    row = ch >> FONTMAP_SHIFT;
    if (subFontPtr->fontMap[row] == NULL) {
	FontMapLoadPage(subFontPtr, row);
    }
    bitOffset = ch & (FONTMAP_BITSPERPAGE - 1);
    subFontPtr->fontMap[row][bitOffset >> 3] |= 1 << (bitOffset & 7);
}

/*
 *-------------------------------------------------------------------------
 *
 * FontMapLoadPage --
 *
 *	Load information about all the characters on a given page. This
 *	information consists of one bit per character that indicates whether
 *	the associated screen font can (1) or cannot (0) display the
 *	characters on the page.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory allocated.
 *
 *-------------------------------------------------------------------------
 */
static void
FontMapLoadPage(
    SubFont *subFontPtr,	/* Contains font mapping cache to be
				 * updated. */
    int row)			/* Index of the page to be loaded into the
				 * cache. */
{
    char buf[16], src[TCL_UTF_MAX];
    int minHi, maxHi, minLo, maxLo, scale, checkLo;
    int i, end, bitOffset, isTwoByteFont, n;
    Tcl_Encoding encoding;
    XFontStruct *fontStructPtr;
    XCharStruct *widths;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    subFontPtr->fontMap[row] = (char *) ckalloc(FONTMAP_BITSPERPAGE / 8);
    memset(subFontPtr->fontMap[row], 0, FONTMAP_BITSPERPAGE / 8);

    if (subFontPtr->familyPtr == &tsdPtr->controlFamily) {
	return;
    }

    fontStructPtr = subFontPtr->fontStructPtr;
    encoding = subFontPtr->familyPtr->encoding;
    isTwoByteFont = subFontPtr->familyPtr->isTwoByteFont;

    widths = fontStructPtr->per_char;
    minHi = fontStructPtr->min_byte1;
    maxHi = fontStructPtr->max_byte1;
    minLo = fontStructPtr->min_char_or_byte2;
    maxLo = fontStructPtr->max_char_or_byte2;
    scale = maxLo - minLo + 1;
    checkLo = minLo;

    if (! isTwoByteFont) {
	if (minLo < 32) {
	    checkLo = 32;
	}
    }

    end = (row + 1) << FONTMAP_SHIFT;
    for (i = row << FONTMAP_SHIFT; i < end; i++) {
	int hi, lo;

	if (Tcl_UtfToExternal(NULL, encoding, src, Tcl_UniCharToUtf(i, src),
		TCL_ENCODING_STOPONERROR, NULL, buf, sizeof(buf), NULL,
		NULL, NULL) != TCL_OK) {
	    continue;
	}
	if (isTwoByteFont) {
	    hi = ((unsigned char *) buf)[0];
	    lo = ((unsigned char *) buf)[1];
	} else {
	    hi = 0;
	    lo = ((unsigned char *) buf)[0];
	}
	if ((hi < minHi) || (hi > maxHi) || (lo < checkLo) || (lo > maxLo)) {
	    continue;
	}
	n = (hi - minHi) * scale + lo - minLo;
	if ((widths == NULL) || (widths[n].width + widths[n].rbearing != 0)) {
	    bitOffset = i & (FONTMAP_BITSPERPAGE - 1);
	    subFontPtr->fontMap[row][bitOffset >> 3] |= 1 << (bitOffset & 7);
	}
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * CanUseFallbackWithAliases --
 *
 *	Helper function for FindSubFontForChar. Determine if the specified
 *	face name (or an alias of the specified face name) can be used to
 *	construct a screen font that can display the given character.
 *
 * Results:
 *	See CanUseFallback().
 *
 * Side effects:
 *	If the name and/or one of its aliases was rejected, the rejected
 *	string is recorded in nameTriedPtr so that it won't be tried again.
 *	The table of SubFonts might be extended, and if a non-NULL reference
 *	to a subfont pointer is available, it is updated if it previously
 *	pointed into the old subfont table.
 *
 *---------------------------------------------------------------------------
 */

static SubFont *
CanUseFallbackWithAliases(
    UnixFont *fontPtr,		/* The font object that will own the new
				 * screen font. */
    char *faceName,		/* Desired face name for new screen font. */
    int ch,			/* The Unicode character that the new screen
				 * font must be able to display. */
    Tcl_DString *nameTriedPtr,	/* Records face names that have already been
				 * tried. It is possible for the same face
				 * name to be queried multiple times when
				 * trying to find a suitable screen font. */
    SubFont **fixSubFontPtrPtr)	/* Subfont reference to fix up if we
				 * reallocate our subfont table. */
{
    SubFont *subFontPtr;
    char **aliases;
    int i;

    if (SeenName(faceName, nameTriedPtr) == 0) {
	subFontPtr = CanUseFallback(fontPtr, faceName, ch, fixSubFontPtrPtr);
	if (subFontPtr != NULL) {
	    return subFontPtr;
	}
    }
    aliases = TkFontGetAliasList(faceName);
    if (aliases != NULL) {
	for (i = 0; aliases[i] != NULL; i++) {
	    if (SeenName(aliases[i], nameTriedPtr) == 0) {
		subFontPtr = CanUseFallback(fontPtr, aliases[i], ch,
			fixSubFontPtrPtr);
		if (subFontPtr != NULL) {
		    return subFontPtr;
		}
	    }
	}
    }
    return NULL;
}

/*
 *---------------------------------------------------------------------------
 *
 * SeenName --
 *
 *	Used to determine we have already tried and rejected the given face
 *	name when looking for a screen font that can support some Unicode
 *	character.
 *
 * Results:
 *	The return value is 0 if this face name has not already been seen,
 *	non-zero otherwise.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static int
SeenName(
    CONST char *name,		/* The name to check. */
    Tcl_DString *dsPtr)		/* Contains names that have already been
				 * seen. */
{
    CONST char *seen, *end;

    seen = Tcl_DStringValue(dsPtr);
    end = seen + Tcl_DStringLength(dsPtr);
    while (seen < end) {
	if (strcasecmp(seen, name) == 0) {
	    return 1;
	}
	seen += strlen(seen) + 1;
    }
    Tcl_DStringAppend(dsPtr, (char *) name, (int) (strlen(name) + 1));
    return 0;
}

/*
 *-------------------------------------------------------------------------
 *
 * CanUseFallback --
 *
 *	If the specified screen font has not already been loaded into the font
 *	object, determine if the specified screen font can display the given
 *	character.
 *
 * Results:
 *	The return value is a pointer to a newly allocated SubFont, owned by
 *	the font object. This SubFont can be used to display the given
 *	character. The SubFont represents the screen font with the base set of
 *	font attributes from the font object, but using the specified face
 *	name. NULL is returned if the font object already holds a reference to
 *	the specified font or if the specified font doesn't exist or cannot
 *	display the given character.
 *
 * Side effects:
 *	The font object's subFontArray is updated to contain a reference to
 *	the newly allocated SubFont. The table of SubFonts might be extended,
 *	and if a non-NULL reference to a subfont pointer is available, it is
 *	updated if it previously pointed into the old subfont table.
 *
 *-------------------------------------------------------------------------
 */

static SubFont *
CanUseFallback(
    UnixFont *fontPtr,		/* The font object that will own the new
				 * screen font. */
    CONST char *faceName,	/* Desired face name for new screen font. */
    int ch,			/* The Unicode character that the new screen
				 * font must be able to display. */
    SubFont **fixSubFontPtrPtr)	/* Subfont reference to fix up if we
				 * reallocate our subfont table. */
{
    int i, nameIdx, numNames, srcLen, numEncodings, bestIdx[2];
    Tk_Uid hateFoundry;
    CONST char *charset, *hateCharset;
    unsigned int bestScore[2];
    char **nameList, **nameListOrig, src[TCL_UTF_MAX];
    FontAttributes want, got;
    Display *display;
    SubFont subFont;
    XFontStruct *fontStructPtr;
    Tcl_DString dsEncodings;
    Tcl_Encoding *encodingCachePtr;

    /*
     * Assume: the face name is times.
     * Assume: adobe:times:iso8859-1 has already been used.
     *
     * Are there any versions of times that can display this character (e.g.,
     *    perhaps linotype:times:iso8859-2)?
     *	  a. Get list of all times fonts.
     *	  b1. Cross out all names whose encodings we've already used.
     *	  b2. Cross out all names whose foundry & encoding we've already seen.
     *	  c. Cross out all names whose encoding cannot handle the character.
     *	  d. Rank each name and pick the best match.
     *	  e. If that font cannot actually display the character, cross out all
     *	     names with the same foundry and encoding and go back to (c).
     */

    display = fontPtr->display;
    nameList = ListFonts(display, faceName, &numNames);
    if (numNames == 0) {
	return NULL;
    }
    nameListOrig = nameList;

    srcLen = Tcl_UniCharToUtf(ch, src);

    want.fa = fontPtr->font.fa;
    want.xa = fontPtr->xa;

    want.fa.family = Tk_GetUid(faceName);
    want.fa.size = -fontPtr->pixelSize;

    hateFoundry = NULL;
    hateCharset = NULL;
    numEncodings = 0;
    Tcl_DStringInit(&dsEncodings);

    charset = NULL;	/* lint, since numNames must be > 0 to get here. */

  retry:
    bestIdx[0] = -1;
    bestIdx[1] = -1;
    bestScore[0] = (unsigned int) -1;
    bestScore[1] = (unsigned int) -1;
    for (nameIdx = 0; nameIdx < numNames; nameIdx++) {
	Tcl_Encoding encoding;
	char dst[16];
	int scalable, srcRead, dstWrote;
	unsigned int score;

	if (nameList[nameIdx] == NULL) {
	    continue;
	}
	if (TkFontParseXLFD(nameList[nameIdx], &got.fa, &got.xa) != TCL_OK) {
	    goto crossout;
	}
	IdentifySymbolEncodings(&got);
	charset = GetEncodingAlias(got.xa.charset);
	if (hateFoundry != NULL) {
	    /*
	     * E. If the font we picked cannot actually display the character,
	     * cross out all names with the same foundry and encoding.
	     */

	    if ((hateFoundry == got.xa.foundry)
		    && (strcmp(hateCharset, charset) == 0)) {
		goto crossout;
	    }
	} else {
	    /*
	     * B. Cross out all names whose encodings we've already used.
	     */

	    for (i = 0; i < fontPtr->numSubFonts; i++) {
		encoding = fontPtr->subFontArray[i].familyPtr->encoding;
		if (strcmp(charset, Tcl_GetEncodingName(encoding)) == 0) {
		    goto crossout;
		}
	    }
	}

	/*
	 * C. Cross out all names whose encoding cannot handle the character.
	 */

	encodingCachePtr = (Tcl_Encoding *) Tcl_DStringValue(&dsEncodings);
	for (i = numEncodings; --i >= 0; encodingCachePtr++) {
	    encoding = *encodingCachePtr;
	    if (strcmp(Tcl_GetEncodingName(encoding), charset) == 0) {
		break;
	    }
	}
	if (i < 0) {
	    encoding = Tcl_GetEncoding(NULL, charset);
	    if (encoding == NULL) {
		goto crossout;
	    }

	    Tcl_DStringAppend(&dsEncodings, (char *) &encoding,
		    sizeof(encoding));
	    numEncodings++;
	}
	Tcl_UtfToExternal(NULL, encoding, src, srcLen,
		TCL_ENCODING_STOPONERROR, NULL, dst, sizeof(dst), &srcRead,
		&dstWrote, NULL);
	if (dstWrote == 0) {
	    goto crossout;
	}

	/*
	 * D. Rank each name and pick the best match.
	 */

	scalable = (got.fa.size == 0);
	score = RankAttributes(&want, &got);
	if (score < bestScore[scalable]) {
	    bestIdx[scalable] = nameIdx;
	    bestScore[scalable] = score;
	}
	if (score == 0) {
	    break;
	}
	continue;

    crossout:
	if (nameList == nameListOrig) {
	    /*
	     * Not allowed to change pointers to memory that X gives you, so
	     * make a copy.
	     */

	    nameList = (char **) ckalloc(numNames * sizeof(char *));
	    memcpy(nameList, nameListOrig, numNames * sizeof(char *));
	}
	nameList[nameIdx] = NULL;
    }

    fontStructPtr = GetScreenFont(display, &want, nameList, bestIdx,
	    bestScore);

    encodingCachePtr = (Tcl_Encoding *) Tcl_DStringValue(&dsEncodings);
    for (i = numEncodings; --i >= 0; encodingCachePtr++) {
	Tcl_FreeEncoding(*encodingCachePtr);
    }
    Tcl_DStringFree(&dsEncodings);
    numEncodings = 0;

    if (fontStructPtr == NULL) {
	if (nameList != nameListOrig) {
	    ckfree((char *) nameList);
	}
	XFreeFontNames(nameListOrig);
	return NULL;
    }

    InitSubFont(display, fontStructPtr, 0, &subFont);
    if (FontMapLookup(&subFont, ch) == 0) {
	/*
	 * E. If the font we picked cannot actually display the character,
	 * cross out all names with the same foundry and encoding and pick
	 * another font.
	 */

	hateFoundry = got.xa.foundry;
	hateCharset = charset;
	ReleaseSubFont(display, &subFont);
	goto retry;
    }
    if (nameList != nameListOrig) {
	ckfree((char *) nameList);
    }
    XFreeFontNames(nameListOrig);

    if (fontPtr->numSubFonts >= SUBFONT_SPACE) {
	SubFont *newPtr;

	newPtr = (SubFont *)
		ckalloc(sizeof(SubFont) * (fontPtr->numSubFonts + 1));
	memcpy((char *) newPtr, fontPtr->subFontArray,
		fontPtr->numSubFonts * sizeof(SubFont));
	if (fixSubFontPtrPtr != NULL) {
	    register SubFont *fixSubFontPtr = *fixSubFontPtrPtr;

	    if (fixSubFontPtr != &fontPtr->controlSubFont) {
		*fixSubFontPtrPtr =
			newPtr + (fixSubFontPtr - fontPtr->subFontArray);
	    }
	}
	if (fontPtr->subFontArray != fontPtr->staticSubFonts) {
	    ckfree((char *) fontPtr->subFontArray);
	}
	fontPtr->subFontArray = newPtr;
    }
    fontPtr->subFontArray[fontPtr->numSubFonts] = subFont;
    fontPtr->numSubFonts++;
    return &fontPtr->subFontArray[fontPtr->numSubFonts - 1];
}

/*
 *---------------------------------------------------------------------------
 *
 * RankAttributes --
 *
 *	Determine how close the attributes of the font in question match the
 *	attributes that we want.
 *
 * Results:
 *	The return value is the score; lower numbers are better. *scalablePtr
 *	is set to 0 if the font was not scalable, 1 otherwise.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static unsigned int
RankAttributes(
    FontAttributes *wantPtr,	/* The desired attributes. */
    FontAttributes *gotPtr)	/* The attributes we have to live with. */
{
    unsigned int penalty;

    penalty = 0;
    if (gotPtr->xa.foundry != wantPtr->xa.foundry) {
	penalty += 4500;
    }
    if (gotPtr->fa.family != wantPtr->fa.family) {
	penalty += 9000;
    }
    if (gotPtr->fa.weight != wantPtr->fa.weight) {
	penalty += 90;
    }
    if (gotPtr->fa.slant != wantPtr->fa.slant) {
	penalty += 60;
    }
    if (gotPtr->xa.slant != wantPtr->xa.slant) {
	penalty += 10;
    }
    if (gotPtr->xa.setwidth != wantPtr->xa.setwidth) {
	penalty += 1000;
    }

    if (gotPtr->fa.size == 0) {
	/*
	 * A scalable font is almost always acceptable, but the corresponding
	 * bitmapped font would be better.
	 */

	penalty += 10;
    } else {
	int diff;

	/*
	 * It's worse to be too large than to be too small.
	 */

	diff = (-gotPtr->fa.size - -wantPtr->fa.size);
	if (diff > 0) {
	    penalty += 600;
	} else if (diff < 0) {
	    penalty += 150;
	    diff = -diff;
	}
	penalty += 150 * diff;
    }
    if (gotPtr->xa.charset != wantPtr->xa.charset) {
	int i;
	CONST char *gotAlias, *wantAlias;

	penalty += 65000;
	gotAlias = GetEncodingAlias(gotPtr->xa.charset);
	wantAlias = GetEncodingAlias(wantPtr->xa.charset);
	if (strcmp(gotAlias, wantAlias) != 0) {
	    penalty += 30000;
	    for (i = 0; encodingList[i] != NULL; i++) {
		if (strcmp(gotAlias, encodingList[i]) == 0) {
		    penalty -= 30000;
		    break;
		}
		penalty += 20000;
	    }
	}
    }
    return penalty;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetScreenFont --
 *
 *	Given the names for the best scalable and best bitmapped font,
 *	actually construct an XFontStruct based on the best XLFD. This is
 *	where all the alias and fallback substitution bottoms out.
 *
 * Results:
 *	The screen font that best corresponds to the set of attributes.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static XFontStruct *
GetScreenFont(
    Display *display,		/* Display for new XFontStruct. */
    FontAttributes *wantPtr,	/* Contains desired actual pixel-size if the
				 * best font was scalable. */
    char **nameList,		/* Array of XLFDs. */
    int bestIdx[2],		/* Indices into above array for XLFD of best
				 * bitmapped and best scalable font. */
    unsigned int bestScore[2])	/* Scores of best bitmapped and best scalable
				 * font. XLFD corresponding to lowest score
				 * will be constructed. */
{
    XFontStruct *fontStructPtr;

    if ((bestIdx[0] < 0) && (bestIdx[1] < 0)) {
	return NULL;
    }

    /*
     * Now we know which is the closest matching scalable font and the closest
     * matching bitmapped font. If the scalable font was a better match, try
     * getting the scalable font; however, if the scalable font was not
     * actually available in the desired pointsize, fall back to the closest
     * bitmapped font.
     */

    fontStructPtr = NULL;
    if (bestScore[1] < bestScore[0]) {
	char *str, *rest, buf[256];
	int i;

	/*
	 * Fill in the desired pixel size for this font.
	 */

    tryscale:
	str = nameList[bestIdx[1]];
	for (i = 0; i < XLFD_PIXEL_SIZE; i++) {
	    str = strchr(str + 1, '-');
	}
	rest = str;
	for (i = XLFD_PIXEL_SIZE; i < XLFD_CHARSET; i++) {
	    rest = strchr(rest + 1, '-');
	}
	*str = '\0';
	sprintf(buf, "%.200s-%d-*-*-*-*-*%s", nameList[bestIdx[1]],
		-wantPtr->fa.size, rest);
	*str = '-';
	fontStructPtr = XLoadQueryFont(display, buf);
	bestScore[1] = INT_MAX;
    }
    if (fontStructPtr == NULL) {
	fontStructPtr = XLoadQueryFont(display, nameList[bestIdx[0]]);
	if (fontStructPtr == NULL) {
	    /*
	     * This shouldn't happen because the font name is one of the names
	     * that X gave us to use, but it does anyhow.
	     */

	    if (bestScore[1] < INT_MAX) {
		goto tryscale;
	    }
	    return GetSystemFont(display);
	}
    }
    return fontStructPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetSystemFont --
 *
 *	Absolute fallback mechanism, called when we need a font and no other
 *	font can be found and/or instantiated.
 *
 * Results:
 *	A pointer to a font. Never NULL.
 *
 * Side effects:
 *	If there are NO fonts installed on the system, this call will panic,
 *	but how did you get X running in that case?
 *
 *---------------------------------------------------------------------------
 */

static XFontStruct *
GetSystemFont(
    Display *display)		/* Display for new XFontStruct. */
{
    XFontStruct *fontStructPtr;

    fontStructPtr = XLoadQueryFont(display, "fixed");
    if (fontStructPtr == NULL) {
	fontStructPtr = XLoadQueryFont(display, "*");
	if (fontStructPtr == NULL) {
	    Tcl_Panic("TkpGetFontFromAttributes: cannot get any font");
	}
    }
    return fontStructPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetFontAttributes --
 *
 *	Given a screen font, determine its actual attributes, which are not
 *	necessarily the attributes that were used to construct it.
 *
 * Results:
 *	*faPtr is filled with the screen font's attributes.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static int
GetFontAttributes(
    Display *display,		/* Display that owns the screen font. */
    XFontStruct *fontStructPtr,	/* Screen font to query. */
    FontAttributes *faPtr)	/* For storing attributes of screen font. */
{
    unsigned long value;
    char *name;

    if ((XGetFontProperty(fontStructPtr, XA_FONT, &value) != False) &&
	    (value != 0)) {
	name = XGetAtomName(display, (Atom) value);
	if (TkFontParseXLFD(name, &faPtr->fa, &faPtr->xa) != TCL_OK) {
	    faPtr->fa.family = Tk_GetUid(name);
	    faPtr->xa.foundry = Tk_GetUid("");
	    faPtr->xa.charset = Tk_GetUid("");
	}
	XFree(name);
    } else {
	TkInitFontAttributes(&faPtr->fa);
	TkInitXLFDAttributes(&faPtr->xa);
    }

    /*
     * Do last ditch check for family. It seems that some X servers can fail
     * on the X font calls above, slipping through earlier checks. X-Win32 5.4
     * is one of these.
     */

    if (faPtr->fa.family == NULL) {
	faPtr->fa.family = Tk_GetUid("");
	faPtr->xa.foundry = Tk_GetUid("");
	faPtr->xa.charset = Tk_GetUid("");
    }
    return IdentifySymbolEncodings(faPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * ListFonts --
 *
 *	Utility function to return the array of all XLFDs on the system with
 *	the specified face name.
 *
 * Results:
 *	The return value is an array of XLFDs, which should be freed with
 *	XFreeFontNames(), or NULL if no XLFDs matched the requested name.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static char **
ListFonts(
    Display *display,		/* Display to query. */
    CONST char *faceName,	/* Desired face name, or "*" for all. */
    int *numNamesPtr)		/* Filled with length of returned array, or 0
				 * if no names were found. */
{
    char buf[256];

    sprintf(buf, "-*-%.80s-*-*-*-*-*-*-*-*-*-*-*-*", faceName);
    return XListFonts(display, buf, 10000, numNamesPtr);
}

static char **
ListFontOrAlias(
    Display *display,		/* Display to query. */
    CONST char *faceName,	/* Desired face name, or "*" for all. */
    int *numNamesPtr)		/* Filled with length of returned array, or 0
				 * if no names were found. */
{
    char **nameList, **aliases;
    int i;

    nameList = ListFonts(display, faceName, numNamesPtr);
    if (nameList != NULL) {
	return nameList;
    }
    aliases = TkFontGetAliasList(faceName);
    if (aliases != NULL) {
	for (i = 0; aliases[i] != NULL; i++) {
	    nameList = ListFonts(display, aliases[i], numNamesPtr);
	    if (nameList != NULL) {
		return nameList;
	    }
	}
    }
    *numNamesPtr = 0;
    return NULL;
}

/*
 *---------------------------------------------------------------------------
 *
 * IdentifySymbolEncodings --
 *
 *	If the font attributes refer to a symbol font, update the charset
 *	field of the font attributes so that it reflects the encoding of that
 *	symbol font. In general, the raw value for the charset field parsed
 *	from an XLFD is meaningless for symbol fonts.
 *
 *	Symbol fonts are all fonts whose name appears in the symbolClass.
 *
 * Results:
 *	The return value is non-zero if the font attributes specify a symbol
 *	font, or 0 otherwise. If a non-zero value is returned the charset
 *	field of the font attributes will be changed to the string that
 *	represents the actual encoding for the symbol font.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static int
IdentifySymbolEncodings(
    FontAttributes *faPtr)
{
    int i, j;
    char **aliases, **symbolClass;

    symbolClass = TkFontGetSymbolClass();
    for (i = 0; symbolClass[i] != NULL; i++) {
	if (strcasecmp(faPtr->fa.family, symbolClass[i]) == 0) {
	    faPtr->xa.charset = Tk_GetUid(GetEncodingAlias(symbolClass[i]));
	    return 1;
	}
	aliases = TkFontGetAliasList(symbolClass[i]);
	for (j = 0; (aliases != NULL) && (aliases[j] != NULL); j++) {
	    if (strcasecmp(faPtr->fa.family, aliases[j]) == 0) {
		faPtr->xa.charset = Tk_GetUid(GetEncodingAlias(aliases[j]));
		return 1;
	    }
	}
    }
    return 0;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetEncodingAlias --
 *
 *	Map the name of an encoding to another name that should be used when
 *	actually loading the encoding. For instance, the encodings
 *	"jisc6226.1978", "jisx0208.1983", "jisx0208.1990", and "jisx0208.1996"
 *	are well-known names for the same encoding and are represented by one
 *	encoding table: "jis0208".
 *
 * Results:
 *	As above. If the name has no alias, the original name is returned.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static CONST char *
GetEncodingAlias(
    CONST char *name)		/* The name to look up. */
{
    EncodingAlias *aliasPtr;

    for (aliasPtr = encodingAliases; aliasPtr->aliasPattern != NULL; ) {
	if (Tcl_StringMatch((char *) name, aliasPtr->aliasPattern)) {
	    return aliasPtr->realName;
	}
	aliasPtr++;
    }
    return name;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
