The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * $Id: tkUnixRFont.c,v 1.5 2003/08/31 20:25:12 keithp Exp $
 *
 * Copyright © 2003 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include "tkUnixInt.h"
#include "tkFont.h"

#ifdef USE_XFT_FONTS

#include <X11/Xft/Xft.h>
#include <ctype.h>

typedef struct _UnixFtFace {
    XftFont	    *ftFont;
    FcPattern	    *source;
    FcCharSet	    *charset;
} UnixFtFace;

typedef struct _UnixFtFont {
    TkFont	    font;    	/* Stuff used by generic font package.  Must
				 * be first in structure. */
    UnixFtFace	    *faces;
    int		    nfaces;
    FcCharSet	    *charset;
    FcPattern	    *pattern;

    Display	    *display;
    int		    screen;
    XftDraw	    *ftDraw;
    Drawable	    drawable;
    XftColor	    color;
} UnixFtFont;

void
TkpFontPkgInit(mainPtr)
    TkMainInfo *mainPtr;	/* The application being created. */
{
}

static XftFont *
GetFont (UnixFtFont *fontPtr, FcChar32 ucs4)
{
    int	    i;

    if (ucs4)
    {
	for (i = 0; i < fontPtr->nfaces; i++)
	{
	    FcCharSet   *charset = fontPtr->faces[i].charset;
	    if (charset && FcCharSetHasChar (charset, ucs4))
		break;
	}
	if (i == fontPtr->nfaces)
	    i = 0;
    }
    else
	i = 0;
    if (!fontPtr->faces[i].ftFont)
    {
	FcPattern   *pat = FcFontRenderPrepare (0, fontPtr->pattern,
						fontPtr->faces[i].source);
						
	fontPtr->faces[i].ftFont = XftFontOpenPattern (fontPtr->display,
						       pat);
    }
    return fontPtr->faces[i].ftFont;
}

static void
FiniFont (UnixFtFont *fontPtr)
{
    Display	    *display = fontPtr->display;
    Tk_ErrorHandler handler;
    int		    i;

    handler = Tk_CreateErrorHandler(display, -1, -1, -1,
				    (Tk_ErrorProc *) NULL, (ClientData) NULL);
    for (i = 0; i < fontPtr->nfaces; i++)
    {
	if (fontPtr->faces[i].ftFont) {
	    XftFontClose (fontPtr->display, fontPtr->faces[i].ftFont);
#if 0
	    /* Doing this clears a leak but can also core ... */
	    FcPatternDestroy (fontPtr->faces[i].ftFont->pattern);
#endif
	}
	if (fontPtr->faces[i].source)
	    FcPatternDestroy (fontPtr->faces[i].source);
	if (fontPtr->faces[i].charset)
	    FcCharSetDestroy (fontPtr->faces[i].charset);
    }

    fontPtr->nfaces  = 0;
    if (fontPtr->faces) {
	ckfree((char *) fontPtr->faces);
	fontPtr->faces = NULL;
    }

    if (fontPtr->charset) {
	FcCharSetDestroy (fontPtr->charset);
	fontPtr->charset = NULL;
    }

    if (fontPtr->pattern) {
	FcPatternDestroy (fontPtr->pattern);
	fontPtr->pattern = NULL;
    }


    if (fontPtr->ftDraw) {
	XftDrawDestroy (fontPtr->ftDraw);
	fontPtr->ftDraw = NULL;
    }

    if (fontPtr->font.fid) {
	XUnloadFont (fontPtr->display, fontPtr->font.fid);
	fontPtr->font.fid = None;
    }

    Tk_DeleteErrorHandler(handler);
}

static UnixFtFont *
InitFont (UnixFtFont *fontPtr, Tk_Window tkwin, FcPattern *pattern)
{
    TkFontAttributes	*faPtr;
    TkFontMetrics	*fmPtr;
    char		*family;
    int			weight, slant;
    double		dSize;
    int			size;
    int			spacing;
    FcFontSet		*set = NULL;
    FcCharSet		*charset = NULL;
    FcResult		result;
    int			i;
    XftFont		*ftFont = NULL;
    UnixFtFace		*faces = NULL;

    FcConfigSubstitute (0, pattern, FcMatchPattern);
    XftDefaultSubstitute (Tk_Display (tkwin),
			  Tk_ScreenNumber (tkwin),
			  pattern);

    /*
     * Generate the list of fonts
     */
    set = FcFontSort (0, pattern, FcTrue, &charset, &result);

    if (!set)
    {
	goto freePattern;
    }


#if 0
    FcPatternPrint(pattern);
#endif

    faces = (UnixFtFace *) ckalloc (set->nfont * sizeof (UnixFtFace));
    if (!faces)
    {
   freeSet:
	FcFontSetDestroy (set);
   freePattern:
	FcCharSetDestroy (charset);
	FcPatternDestroy (pattern);
	return NULL;
    }

#if 0
    FcFontSetPrint(set);
#endif

    if (fontPtr) {
	FiniFont(fontPtr);
    }
    else {
	fontPtr = (UnixFtFont *) ckalloc (sizeof (UnixFtFont));
    }
    if (!fontPtr) {
	if (faces)
	    ckfree((char *) faces);
	goto freeSet;
    }

    fontPtr->charset = charset;
    fontPtr->pattern = pattern;
    fontPtr->faces   = faces;
    fontPtr->nfaces  = set->nfont;

    /*
     * Fill in information about each returned font
     */
    for (i = 0; i < set->nfont; i++)
    {
	fontPtr->faces[i].ftFont = 0;
	fontPtr->faces[i].source = set->fonts[i];
	if (FcPatternGetCharSet (set->fonts[i], FC_CHARSET, 0, &charset) == FcResultMatch)
	    fontPtr->faces[i].charset = FcCharSetCopy (charset);
	else
	    fontPtr->faces[i].charset = 0;
    }

    FcFontSetDestroy (set);

    fontPtr->font.fid	= XLoadFont (Tk_Display (tkwin), "fixed");

    fontPtr->display	= Tk_Display (tkwin);
    fontPtr->screen	= Tk_ScreenNumber (tkwin);
    fontPtr->ftDraw	= 0;
    fontPtr->drawable	= 0;
    fontPtr->color.color.red  = 0;
    fontPtr->color.color.green= 0;
    fontPtr->color.color.blue = 0;
    fontPtr->color.color.alpha= 0xffff;
    fontPtr->color.pixel = 0xffffffff;

    faPtr		= &fontPtr->font.fa;

    /* Now get 0'th font */
    ftFont              = GetFont (fontPtr, 0);

    /* And set Tk's actuals from its pattern */
    pattern = ftFont->pattern;

    /*
     * Build the Tk font structure
     */
    if (XftPatternGetString (pattern, XFT_FAMILY, 0, &family) != XftResultMatch)
	family = "Unknown";

    if (XftPatternGetInteger (pattern, XFT_WEIGHT, 0, &weight) != XftResultMatch)
	weight = XFT_WEIGHT_MEDIUM;
    if (weight <= XFT_WEIGHT_MEDIUM)
	weight = TK_FW_NORMAL;
    else
	weight = TK_FW_BOLD;

    if (XftPatternGetInteger (pattern, XFT_SLANT, 0, &slant) != XftResultMatch)
	slant = XFT_SLANT_ROMAN;
    if (slant <= XFT_SLANT_ROMAN)
	slant = TK_FS_ROMAN;
    else
	slant = TK_FS_ITALIC;

    if (XftPatternGetDouble (pattern, XFT_PIXEL_SIZE, 0, &dSize)
				!= XftResultMatch) {
	if (XftPatternGetDouble (pattern, XFT_SIZE, 0, &dSize)
				!= XftResultMatch) {
	    size = -12;
	}
	else {
	    size = (int)(dSize+0.5);
	}
    }
    else {
	size = -(int)(dSize+0.5);
    }

    if (XftPatternGetInteger (pattern, XFT_SPACING, 0, &spacing) != XftResultMatch)
	spacing = XFT_PROPORTIONAL;
    if (spacing == XFT_PROPORTIONAL)
	spacing = 0;
    else
	spacing = 1;
#if 0
    printf ("%p family %s size %g=>%d weight %d slant %d id=%d\n",
            fontPtr, family, dSize, size, weight, slant, fontPtr->font.fid);
#endif

    faPtr->family	= family;
    faPtr->size		= size;
    faPtr->weight	= weight;
    faPtr->slant	= slant;
    faPtr->underline	= 0;
    faPtr->overstrike	= 0;

    fmPtr		= &fontPtr->font.fm;
    fmPtr->ascent	= ftFont->ascent;
    fmPtr->descent	= ftFont->descent;
    fmPtr->maxWidth	= ftFont->max_advance_width;
    fmPtr->fixed	= spacing;

    return fontPtr;
}

TkFont *
TkpGetNativeFont(tkwin, name)
    Tk_Window tkwin;		/* For display where font will be used. */
    CONST char *name;		/* Platform-specific font name. */
{
    UnixFtFont	*fontPtr = NULL;
    FcPattern	*pattern = NULL;
#if 0
    printf ("TkpGetNativeFont '%s'\n", name);
#endif
    if (*name == '-') {
#if 0
	/* This parses full names ok, but result is mis-scaled for some reason */
	pattern = XftXlfdParse (name, FcFalse, FcFalse);
#endif
	if (!pattern) {
	    TkFontAttributes fa;
	    TkXLFDAttributes xa;
	    if (TkFontParseXLFD(name, &fa, &xa) != TCL_OK) {
		return NULL;
	    }
	    else {
		return TkpGetFontFromAttributes(NULL, tkwin, &fa);
	    }
	}
    }
    else if (strpbrk(name,":,=") || !strpbrk(name," {")) {
	pattern = FcNameParse (name);
    }
    if (pattern) {
	fontPtr = InitFont (NULL, tkwin, pattern);
    }
    else {
	
    }
    if (fontPtr) {
	return &fontPtr->font;
    }
    return NULL;
}

TkFont *
TkpGetFontFromAttributes(tkFontPtr, tkwin, faPtr)
    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. */
{
    XftPattern	*pattern;
    int		weight, slant;
    UnixFtFont	*fontPtr = (UnixFtFont *)tkFontPtr;

    pattern = XftPatternBuild (0,
			       XFT_FAMILY, XftTypeString, faPtr->family,
			       0);
    if (faPtr->size > 0)
	XftPatternAddInteger (pattern, XFT_SIZE, faPtr->size);
    else
	XftPatternAddInteger (pattern, XFT_PIXEL_SIZE, -faPtr->size);
    switch (faPtr->weight) {
    case TK_FW_NORMAL:
    default:
	weight = XFT_WEIGHT_MEDIUM;
	break;
    case TK_FW_BOLD:
	weight = XFT_WEIGHT_BOLD;
	break;
    }
    XftPatternAddInteger (pattern, XFT_WEIGHT, weight);
    switch (faPtr->slant) {
    case TK_FS_ROMAN:
    default:
	slant = XFT_SLANT_ROMAN;
	break;
    case TK_FS_ITALIC:
	slant = XFT_SLANT_ITALIC;
	break;
    case TK_FS_OBLIQUE:
	slant = XFT_SLANT_OBLIQUE;
	break;
    }
    XftPatternAddInteger (pattern, XFT_SLANT, slant);

    fontPtr = InitFont (fontPtr, tkwin, pattern);
#if 0
    printf ("%p TkpGetFontFromAttributes %s-%d %d %d\n", fontPtr,
            faPtr->family, faPtr->size, faPtr->weight, faPtr->slant);
#endif
    if (!fontPtr)
	return NULL;
    return &fontPtr->font;
}

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

    FiniFont (fontPtr);
    /* XXX tkUnixFont.c doesn't free tkFontPtr... */
}

void
TkpGetFontFamilies(interp, tkwin)
    Tcl_Interp *interp;		/* Interp to hold result. */
    Tk_Window tkwin;		/* For display to query. */
{
    Tcl_Obj	*resultPtr, *strPtr;
    XftFontSet	*list;
    int		i;
    char	*family;

    resultPtr = Tcl_GetObjResult(interp);

    list = XftListFonts (Tk_Display (tkwin),
			 Tk_ScreenNumber (tkwin),
			 0,
			 XFT_FAMILY,
			 0);
    for (i = 0; i < list->nfont; i++)
    {
	if (XftPatternGetString (list->fonts[i], XFT_FAMILY, 0, &family) == XftResultMatch)
	{
	    strPtr = Tcl_NewStringObj(Tk_GetUid (family), -1);
	    Tcl_ListObjAppendElement (NULL, resultPtr, strPtr);
	}
    }
    XftFontSetDestroy (list);
}

void
TkpGetSubFonts(interp, tkfont)
    Tcl_Interp *interp;
    Tk_Font tkfont;
{
    Tcl_Obj	*objv[4];
    Tcl_Obj	*resultPtr, *listPtr;
    UnixFtFont	*fontPtr = (UnixFtFont *) tkfont;
    char	*family, *foundry, *encoding, *file;
    XftFont	*ftFont = GetFont (fontPtr, 0);
    int i;
    int used;
    FcPattern *pattern;

    resultPtr = Tcl_GetObjResult(interp);
    for (i = 0; i < fontPtr->nfaces; i++) {
	if (fontPtr->faces[i].ftFont) {
	    pattern = fontPtr->faces[i].ftFont->pattern;
	    used = 1;
	}
	else {
	    pattern = fontPtr->faces[i].source;
	    used = 0;
	    continue;
	}
#if 0
	FcPatternPrint(pattern);
#endif
	if (XftPatternGetString (pattern, XFT_FAMILY,
			     0, &family) != XftResultMatch)
	    {
		family = "";
	    }
	if (XftPatternGetString (pattern, XFT_FOUNDRY,
			     0, &foundry) != XftResultMatch)
	    {
		foundry = "";
	    }
	if (XftPatternGetString (pattern, XFT_ENCODING,
			     0, &encoding))
	    {
		encoding = "";
	    }
	if (XftPatternGetString (pattern, XFT_FILE,
			     0, &file))
	    {
		file = "";
	    }
	objv[0] = Tcl_NewStringObj(family, -1);
	objv[1] = Tcl_NewStringObj(file, -1);
	objv[2] = Tcl_NewStringObj(foundry, -1);
	objv[3] = Tcl_NewStringObj(encoding, -1);
	listPtr = Tcl_NewListObj (4, objv);
	Tcl_ListObjAppendElement (interp, resultPtr, listPtr);
    }
}

int
Tk_MeasureChars(tkfont, source, numBytes, maxLength, flags, lengthPtr)
    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. */
{
    UnixFtFont	    *fontPtr = (UnixFtFont *) tkfont;
    XftFont	    *ftFont;
    FcChar32	    c;
    int		    clen;
    XGlyphInfo	    extents;
    int		    curX, newX;
    int		    termByte = 0;
    int		    termX = 0;
    int		    curByte, newByte;
    int		    sawNonSpace;

    curX = 0;
    curByte = 0;
    sawNonSpace = 0;

    while (numBytes > 0)
    {
	c = 0;
	clen = FcUtf8ToUcs4 ((FcChar8 *) source, &c, numBytes);
	if (clen <= 0) {
	    /* Not supposed to happen - did happen in Text widget
	     * abort() for now - could also take 1byte == 1 char ?
	     */
	    LangDebug("numByte=%d s='%.*s' c=%x clen=%d\n",
                       numBytes, numBytes, source, c, clen);
	    abort();
	}
	source += clen;
	numBytes -= clen;
	if (c < 256 && isspace (c))
	{
	    if (sawNonSpace)
	    {
		termByte = curByte;
		termX = curX;
		sawNonSpace = 0;
	    }
	}
	else
	    sawNonSpace = 1;

	ftFont = GetFont (fontPtr, c);

	XftTextExtents32 (fontPtr->display, ftFont, &c, 1, &extents);

	newX    = curX + extents.xOff;
	newByte = curByte + clen;

#if 0
        LangDebug("%s ch=%d x=%d+w=%d => %d out of %d, curB=%d newB=%d leaving %d\n",
                  __FUNCTION__,c,curX,extents.xOff,newX,maxLength,
                  curByte, newByte, numBytes);
#endif

	if (maxLength >= 0 && newX > maxLength)
	{
	    if ((flags & TK_PARTIAL_OK) ||
		((flags & TK_AT_LEAST_ONE) && curByte == 0))
	    {
		curX = newX;
		curByte = newByte;
	    }
	    else if ((flags & TK_WHOLE_WORDS) && termByte != 0)
	    {
		curX = termX;
		curByte = termByte;
	    }
	    break;
	}
	
	curX     = newX;
	curByte  = newByte;
    }
    *lengthPtr = curX;
    return curByte;
}

#define NUM_SPEC    1024

void
Tk_DrawChars(display, drawable, gc, tkfont, source, numBytes, x, y)
    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, y;			/* Coordinates at which to place origin of
				 * string when drawing. */
{
    UnixFtFont		*fontPtr = (UnixFtFont *) tkfont;
    XGCValues		values;
    XColor		xcolor;
    int			clen;
    XftGlyphFontSpec	specs[NUM_SPEC];
    int			nspec;
    XGlyphInfo		metrics;

#if 0
    printf("%p DrawChars '%.*s'\n",fontPtr,numBytes,source);
#endif

    if (fontPtr->ftDraw == 0)
    {
#if 0
	printf ("Switch to drawable 0x%x\n", drawable);
#endif
	fontPtr->ftDraw = XftDrawCreate (display,
					 drawable,
					 DefaultVisual (display,
							fontPtr->screen),
					 DefaultColormap (display,
							  fontPtr->screen));
	fontPtr->drawable = drawable;
    }
    /* Seem to need this even if drawable is same ? */
    else /* if (fontPtr->drawable != drawable) */
    {
	Tk_ErrorHandler handler;

        handler = Tk_CreateErrorHandler(display, -1, -1, -1,
					(Tk_ErrorProc *) NULL, (ClientData) NULL);
	XftDrawChange (fontPtr->ftDraw, drawable);
	fontPtr->drawable = drawable;
	Tk_DeleteErrorHandler(handler);
    }
    XGetGCValues (display, gc, GCForeground, &values);
    if (values.foreground != fontPtr->color.pixel)
    {
	xcolor.pixel = values.foreground;
	XQueryColor (display, DefaultColormap (display,
					       fontPtr->screen),
		     &xcolor);
	fontPtr->color.color.red = xcolor.red;
	fontPtr->color.color.green = xcolor.green;
	fontPtr->color.color.blue = xcolor.blue;
	fontPtr->color.color.alpha = 0xffff;
	fontPtr->color.pixel = values.foreground;
    }
    nspec = 0;
    while (numBytes > 0)
    {
	XftFont	    *ftFont;
	FcChar32    c;
	
	clen = FcUtf8ToUcs4 ((FcChar8 *) source, &c, numBytes);
	source += clen;
	numBytes -= clen;

	ftFont = GetFont (fontPtr, c);
	if (ftFont)
	{
	    specs[nspec].font = ftFont;
	    specs[nspec].glyph = XftCharIndex (fontPtr->display, ftFont, c);
	    specs[nspec].x = x;
	    specs[nspec].y = y;

	    XftGlyphExtents (fontPtr->display, ftFont, &specs[nspec].glyph,
			     1, &metrics);
	    x += metrics.xOff;
	    y += metrics.yOff;
	    nspec++;
	    if (nspec == NUM_SPEC)
	    {
		XftDrawGlyphFontSpec (fontPtr->ftDraw, &fontPtr->color,
				      specs, nspec);
		nspec = 0;
	    }
	}
    }
    if (nspec)
	XftDrawGlyphFontSpec (fontPtr->ftDraw, &fontPtr->color,
			      specs, nspec);
}

#endif /* USE_XFT_FONTS */