/* 
 * tkWinFont.c --
 *
 *	Contains the Windows implementation of the platform-independant
 *	font package interface.
 *
 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
 * Copyright (c) 1994 Software Research Associates, Inc. 
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tkWinFont.c,v 1.13 1999/01/26 04:11:44 jingham Exp $
 */

#include "tkWinInt.h"
#include "tkFont.h"

/*
 * The following structure represents Windows' implementation of a font.
 */

typedef struct WinFont {
    TkFont font;		/* Stuff used by generic font package.  Must
				 * be first in structure. */
    HFONT hFont;		/* Windows information about font. */
    HWND hwnd;			/* Toplevel window of application that owns
				 * this font, used for getting HDC. */
} WinFont;

/*
 * The following structure is used as to map between the Tcl strings
 * that represent the system fonts and the numbers used by Windows.
 */

static TkStateMap systemMap[] = {
    {ANSI_FIXED_FONT,	    "ansifixed"},
    {ANSI_VAR_FONT,	    "ansi"},
    {DEVICE_DEFAULT_FONT,   "device"},
    {OEM_FIXED_FONT,	    "oemfixed"},
    {SYSTEM_FIXED_FONT,	    "systemfixed"},
    {SYSTEM_FONT,	    "system"},
    {-1,		    NULL}
};

/* CYGNUS LOCAL: Map magic windows font names into offsets into a
   NONCLIENTMETRICS structure.  */

static TkStateMap nonClientMap[] = {
    {Tk_Offset(NONCLIENTMETRICS, lfCaptionFont),	"caption"},
    {Tk_Offset(NONCLIENTMETRICS, lfSmCaptionFont),	"smcaption"},
    {Tk_Offset(NONCLIENTMETRICS, lfMenuFont),		"menu"},
    {Tk_Offset(NONCLIENTMETRICS, lfStatusFont),		"status"},
    {Tk_Offset(NONCLIENTMETRICS, lfMessageFont),	"message"},
    {-1,		    				NULL}
};

#define ABS(x)          (((x) < 0) ? -(x) : (x))

static TkFont *		AllocFont _ANSI_ARGS_((TkFont *tkFontPtr, 
                            Tk_Window tkwin, HFONT hFont));
static char *		GetProperty _ANSI_ARGS_((CONST TkFontAttributes *faPtr,
			    CONST char *option));
static int CALLBACK	WinFontFamilyEnumProc _ANSI_ARGS_((ENUMLOGFONT *elfPtr,
			    NEWTEXTMETRIC *ntmPtr, int fontType,
			    LPARAM lParam));

/* CYGNUS LOCAL: New static function.  */
static int		FontChanged _ANSI_ARGS_((TkFontAttributes *faPtr));

/*
 *---------------------------------------------------------------------------
 *
 * 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 procedure 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:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

TkFont *
TkpGetNativeFont(tkwin, name)
    Tk_Window tkwin;		/* For display where font will be used. */
    CONST char *name;		/* Platform-specific font name. */
{
    int object;
    HFONT hFont;
    
    object = TkFindStateNum(NULL, NULL, systemMap, name);
    if (object < 0) {
	return NULL;
    }
    hFont = GetStockObject(object);
    if (hFont == NULL) {
	panic("TkpGetNativeFont: can't allocate stock font");
    }

    return AllocFont(NULL, tkwin, hFont);
}

/*
 *---------------------------------------------------------------------------
 *
 * 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.  NULL is never returned.
 *
 *	Every call to this procedure 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:
 *	None.
 *
 *---------------------------------------------------------------------------
 */
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. */
{
    int offset;
    LOGFONT lf;
    HFONT hFont;
    Window window;
    HWND hwnd;
    HDC hdc;

    /* CYGNUS LOCAL: Magic handling for fonts in the windows-* family.  */
    if (faPtr->family != NULL
	&& strncmp(faPtr->family, "windows-", 8) == 0
	&& (offset = TkFindStateNum(NULL, NULL, nonClientMap,
				    faPtr->family + 8)) >= 0) {
	NONCLIENTMETRICS ncm;

	ncm.cbSize = sizeof(ncm);
	if (! SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm),
				   (void *) &ncm, 0)) {
	    panic("TkpGetFontFromAttributes: SystemParametersInfo failed");
	}

	lf = *(LOGFONT *)((char *) &ncm + offset);
    } else {
	window = Tk_WindowId(((TkWindow *) tkwin)->mainPtr->winPtr);
	hwnd = (window == None) ? NULL : TkWinGetHWND(window);

	hdc = GetDC(hwnd);
	lf.lfHeight		= -faPtr->pointsize;
	if (lf.lfHeight < 0) {
	    lf.lfHeight = MulDiv(lf.lfHeight, 
		    254 * WidthOfScreen(Tk_Screen(tkwin)),
		    720 * WidthMMOfScreen(Tk_Screen(tkwin)));
	}
	lf.lfWidth		= 0;
	lf.lfEscapement	= 0;
	lf.lfOrientation	= 0;
	lf.lfWeight		= (faPtr->weight == TK_FW_NORMAL) ? FW_NORMAL : FW_BOLD;
	lf.lfItalic		= faPtr->slant;
	lf.lfUnderline	= faPtr->underline;
	lf.lfStrikeOut	= faPtr->overstrike;
	lf.lfCharSet	= DEFAULT_CHARSET;
	lf.lfOutPrecision	= OUT_DEFAULT_PRECIS;
	lf.lfClipPrecision	= CLIP_DEFAULT_PRECIS;
	lf.lfQuality	= DEFAULT_QUALITY;
	lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
	if (faPtr->family == NULL) {
	    lf.lfFaceName[0] = '\0';
	} else {
	    lstrcpyn(lf.lfFaceName, faPtr->family, sizeof(lf.lfFaceName));
	}
	ReleaseDC(hwnd, hdc);

	/*
	 * Replace the standard X and Mac family names with the names that
	 * Windows likes.
	 */

	if ((stricmp(lf.lfFaceName, "Times") == 0)
		|| (stricmp(lf.lfFaceName, "New York") == 0)) {
	    strcpy(lf.lfFaceName, "Times New Roman");
	} else if ((stricmp(lf.lfFaceName, "Courier") == 0)
		|| (stricmp(lf.lfFaceName, "Monaco") == 0)) {
	    strcpy(lf.lfFaceName, "Courier New");
	} else if ((stricmp(lf.lfFaceName, "Helvetica") == 0)
		|| (stricmp(lf.lfFaceName, "Geneva") == 0)) {
	    strcpy(lf.lfFaceName, "Arial");
	}
    }

    hFont = CreateFontIndirect(&lf);
    if (hFont == NULL) {
        hFont = GetStockObject(SYSTEM_FONT);
	if (hFont == NULL) {
	    panic("TkpGetFontFromAttributes: cannot get system font");
	}
    }
    return AllocFont(tkFontPtr, tkwin, hFont);
}

/*
 *---------------------------------------------------------------------------
 *
 * 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(tkFontPtr)
    TkFont *tkFontPtr;		/* Token of font to be deleted. */
{
    WinFont *fontPtr;

    fontPtr = (WinFont *) tkFontPtr;
    DeleteObject(fontPtr->hFont);
    ckfree((char *) fontPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * TkpGetFontFamilies, WinFontEnumFamilyProc --
 *
 *	Return information about the font families that are available
 *	on the display of the given window.
 *
 * Results:
 *	interp->result is modified to hold a list of all the available
 *	font families.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */
 
void
TkpGetFontFamilies(interp, tkwin)
    Tcl_Interp *interp;		/* Interp to hold result. */
    Tk_Window tkwin;		/* For display to query. */
{    
    Window window;
    HWND hwnd;
    HDC hdc;

    window = Tk_WindowId(tkwin);
    hwnd = (window == (Window) NULL) ? NULL : TkWinGetHWND(window);

    hdc = GetDC(hwnd);
    EnumFontFamilies(hdc, NULL, (FONTENUMPROC) WinFontFamilyEnumProc,
	    (LPARAM) interp);
    ReleaseDC(hwnd, hdc);
}

/* ARGSUSED */

static int CALLBACK
WinFontFamilyEnumProc(elfPtr, ntmPtr, fontType, lParam)
    ENUMLOGFONT *elfPtr;	/* Logical-font data. */
    NEWTEXTMETRIC *ntmPtr;	/* Physical-font data (not used). */
    int fontType;		/* Type of font (not used). */
    LPARAM lParam;		/* Interp to hold result. */
{
    Tcl_Interp *interp;

    interp = (Tcl_Interp *) lParam;
    Tcl_AppendElement(interp, elfPtr->elfLogFont.lfFaceName);
    return 1;
}

/*
 *---------------------------------------------------------------------------
 *
 *  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 characters 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(tkfont, source, numChars, maxLength, flags, lengthPtr)
    Tk_Font tkfont;		/* Font in which characters will be drawn. */
    CONST char *source;		/* Characters to be displayed.  Need not be
				 * '\0' terminated. */
    int numChars;		/* Maximum number of characters to consider
				 * from source string. */
    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. */
    int *lengthPtr;		/* Filled with x-location just after the
				 * terminating character. */
{
    WinFont *fontPtr;
    HDC hdc;
    HFONT hFont;
    int curX, curIdx;

    fontPtr = (WinFont *) tkfont;

    hdc = GetDC(fontPtr->hwnd);
    hFont = SelectObject(hdc, fontPtr->hFont);

    if (numChars == 0) {
	curX = 0;
	curIdx = 0;
    } else if (maxLength <= 0) {
	SIZE size;

	GetTextExtentPoint32(hdc, source, numChars, &size);
	curX = size.cx;
	curIdx = numChars;
    } else {
	int max;
	int *partials;
	SIZE size;
	
	partials = (int *) ckalloc(numChars * sizeof (int));
	GetTextExtentExPoint(hdc, source, numChars, maxLength, &max,
		partials, &size);
	
	if ((flags & TK_WHOLE_WORDS) && max < numChars) {
	    int sawSpace;
	    int i;
	    
	    sawSpace = 0;
	    i = max;
	    while (i >= 0 && !isspace(source[i])) {
    		--i;
            }
	    while (i >= 0 && isspace(source[i])) {
		sawSpace = 1;
		--i;
            }

	    /*
	     * If a space char was not found, and the flag for forcing
	     * at least on (or more) chars to be drawn is false, then
	     * set MAX to zero so no text is drawn.  Otherwise, if a
	     * space was found, set max to be one char past the space.
	     */
	    
	    if ((i < 0) && !(flags & TK_AT_LEAST_ONE)) {
		max = 0;
	    } else if (sawSpace) {
		max = i + 1;
	    }
		
	}

	if (max == 0) {
	    curX = 0;
	} else {
	    curX = partials[max - 1];
	}

	if (((flags & TK_PARTIAL_OK) && max < numChars && curX < maxLength)
		|| ((flags & TK_AT_LEAST_ONE) && max == 0 && numChars > 0)) {
	    /* CYGNUS LOCAL - BUG ALERT - We have to pass the bogus length, and
	       the dummyMax parameter, because without them the call crashes on
	       NT/J Service Pack 3 and less.  This is documented in the
	       Microsoft Knowledge Base. */
	  
	    int dummyMax;

	    /*
	     * We want to include the first character that didn't
	     * quite fit.  Call the function again to include the
	     * width of the extra character.
             */
	    
	    GetTextExtentExPoint(hdc, source, max + 1, INT_MAX, &dummyMax,
	            partials, &size);
	    curX = partials[max];
	    ++max;

	} 
		
	ckfree((char *) partials);
	curIdx = max;
    }

    SelectObject(hdc, hFont);
    ReleaseDC(fontPtr->hwnd, hdc);

    *lengthPtr = curX;
    return curIdx;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_DrawChars --
 *
 *	Draw a string of characters on the screen.  
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information gets drawn on the screen.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_DrawChars(display, drawable, gc, tkfont, source, numChars, 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;		/* Characters 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 numChars;		/* Number of characters in string. */
    int x, y;			/* Coordinates at which to place origin of
				 * string when drawing. */
{
    HDC dc;
    HFONT hFont;
    TkWinDCState state;
    WinFont *fontPtr;

    fontPtr = (WinFont *) gc->font;
    display->request++;

    if (drawable == None) {
	return;
    }

    dc = TkWinGetDrawableDC(display, drawable, &state);

    SetROP2(dc, tkpWinRopModes[gc->function]);

    if ((gc->fill_style == FillStippled
	    || gc->fill_style == FillOpaqueStippled)
	    && gc->stipple != None) {
	TkWinDrawable *twdPtr = (TkWinDrawable *)gc->stipple;
	HBRUSH oldBrush, stipple;
	HBITMAP oldBitmap, bitmap;
	HDC dcMem;
	TEXTMETRIC tm;
	SIZE size;

	if (twdPtr->type != TWD_BITMAP) {
	    panic("unexpected drawable type in stipple");
	}

	/*
	 * Select stipple pattern into destination dc.
	 */
	
	dcMem = CreateCompatibleDC(dc);

	stipple = CreatePatternBrush(twdPtr->bitmap.handle);
	SetBrushOrgEx(dc, gc->ts_x_origin, gc->ts_y_origin, NULL);
	oldBrush = SelectObject(dc, stipple);

	SetTextAlign(dcMem, TA_LEFT | TA_TOP);
	SetTextColor(dcMem, gc->foreground);
	SetBkMode(dcMem, TRANSPARENT);
	SetBkColor(dcMem, RGB(0, 0, 0));

        hFont = SelectObject(dcMem, fontPtr->hFont);

	/*
	 * Compute the bounding box and create a compatible bitmap.
	 */

	GetTextExtentPoint(dcMem, source, numChars, &size);
	GetTextMetrics(dcMem, &tm);
	size.cx -= tm.tmOverhang;
	bitmap = CreateCompatibleBitmap(dc, size.cx, size.cy);
	oldBitmap = SelectObject(dcMem, bitmap);

	/*
	 * The following code is tricky because fonts are rendered in multiple
	 * colors.  First we draw onto a black background and copy the white
	 * bits.  Then we draw onto a white background and copy the black bits.
	 * Both the foreground and background bits of the font are ANDed with
	 * the stipple pattern as they are copied.
	 */

	PatBlt(dcMem, 0, 0, size.cx, size.cy, BLACKNESS);
	TextOut(dcMem, 0, 0, source, numChars);
	BitBlt(dc, x, y - tm.tmAscent, size.cx, size.cy, dcMem,
		0, 0, 0xEA02E9);
	PatBlt(dcMem, 0, 0, size.cx, size.cy, WHITENESS);
	TextOut(dcMem, 0, 0, source, numChars);
	BitBlt(dc, x, y - tm.tmAscent, size.cx, size.cy, dcMem,
		0, 0, 0x8A0E06);

	/*
	 * Destroy the temporary bitmap and restore the device context.
	 */

        SelectObject(dcMem, hFont);
	SelectObject(dcMem, oldBitmap);
	DeleteObject(bitmap);
	DeleteDC(dcMem);
	SelectObject(dc, oldBrush);
	DeleteObject(stipple);
    } else {
	SetTextAlign(dc, TA_LEFT | TA_BASELINE);
	SetTextColor(dc, gc->foreground);
	SetBkMode(dc, TRANSPARENT);
	hFont = SelectObject(dc, fontPtr->hFont);
	TextOut(dc, x, y, source, numChars);
        SelectObject(dc, hFont);
    }
    TkWinReleaseDrawableDC(drawable, dc, &state);
}

/*
 *---------------------------------------------------------------------------
 *
 * AllocFont --
 *
 *	Helper for TkpGetNativeFont() and TkpGetFontFromAttributes().
 *	Allocates and intializes the memory for a new TkFont that
 *	wraps the platform-specific data.
 *
 * Results:
 *	Returns pointer to newly constructed TkFont.  
 *
 *	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().
 *
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */ 

static TkFont *
AllocFont(tkFontPtr, tkwin, hFont)
    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. */
    HFONT hFont;		/* Windows information about font. */
{
    HWND hwnd;
    WinFont *fontPtr;
    HDC hdc;
    TEXTMETRIC tm;
    Window window;
    char buf[LF_FACESIZE];
    TkFontAttributes *faPtr;

    if (tkFontPtr != NULL) {
        fontPtr = (WinFont *) tkFontPtr;
        DeleteObject(fontPtr->hFont);
    } else {
        fontPtr = (WinFont *) ckalloc(sizeof(WinFont));
    }
    
    window = Tk_WindowId(((TkWindow *) tkwin)->mainPtr->winPtr);
    hwnd = (window == None) ? NULL : TkWinGetHWND(window);

    hdc = GetDC(hwnd);
    hFont = SelectObject(hdc, hFont);
    GetTextFace(hdc, sizeof(buf), buf);
    GetTextMetrics(hdc, &tm);

    fontPtr->font.fid	= (Font) fontPtr;

    faPtr = &fontPtr->font.fa;
    faPtr->family	= Tk_GetUid(buf);
    faPtr->pointsize	= MulDiv(tm.tmHeight - tm.tmInternalLeading,
	    720 * WidthMMOfScreen(Tk_Screen(tkwin)),
	    254 * WidthOfScreen(Tk_Screen(tkwin)));
    faPtr->weight	= (tm.tmWeight > FW_MEDIUM) ? TK_FW_BOLD : TK_FW_NORMAL;
    faPtr->slant	= (tm.tmItalic != 0) ? TK_FS_ITALIC : TK_FS_ROMAN;
    faPtr->underline	= (tm.tmUnderlined != 0) ? 1 : 0;
    faPtr->overstrike	= (tm.tmStruckOut != 0) ? 1 : 0;

    fontPtr->font.fm.ascent	= tm.tmAscent;
    fontPtr->font.fm.descent	= tm.tmDescent;
    fontPtr->font.fm.maxWidth	= tm.tmMaxCharWidth;
    fontPtr->font.fm.fixed	= !(tm.tmPitchAndFamily & TMPF_FIXED_PITCH);

    hFont = SelectObject(hdc, hFont);
    ReleaseDC(hwnd, hdc);

    fontPtr->hFont		= hFont;
    fontPtr->hwnd		= hwnd;

    return (TkFont *) fontPtr;
}

/* CYGNUS LOCAL: This function is called when one of the non client
   metrics changes.  We don't expect this to happen very often, so we
   always try to update all the known fonts.  */

void
TkWinNCMetricsChanged(tkwin)
    Tk_Window tkwin;
{
    TkUpdateFonts(tkwin, FontChanged);
}

/* This function returns non-zero when passed a font in a magic
   Windows non client font.  */

static int
FontChanged(faPtr)
    TkFontAttributes *faPtr;
{
    return (faPtr->family != NULL
	    && strncmp(faPtr->family, "windows-", 8) == 0
	    && TkFindStateNum(NULL, NULL, nonClientMap,
			      faPtr->family + 8) >= 0);
}
