/* 
 * 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.
 *
 * SCCS: @(#) tkWinFont.c 1.20 97/05/14 15:45:30
 */

#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. */
#if 0
    /* CYGNUS LOCAL: We don't need these.  */
    int widths[256];		/* Widths of first 256 chars in this font. */
#endif
} 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;

    /*
     * On the authority of the Gates Empire, Windows 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.  
     */

    /* CYGNUS LOCAL: That does not seem to be true in Windows NT 4.0.
       I changed the code to use GetTextExtentExPoint.  */

    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 i;

	    /* We need to back up through the word that fits at the
               end of the line, and then back through any space
               characters that may precede that word.  We start
               backing up at MAX, because if MAX is a space we can
               stop at MAX - 1.  */
	    i = max;
	    while (i >= 0 && !isspace(source[i])) {
		--i;
	    }
	    while (i >= 0 && isspace(source[i])) {
		--i;
	    }
	    /* We must have executed at least one --i, so this will
               not set MAX larger than the original value.  */
	    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)) {
	    /* 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, 0, NULL, 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);
#if 0
    /* CYGNUS LOCAL: We don't need these.  */
    GetCharWidth(hdc, 0, 255, fontPtr->widths);
#endif

    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);
}
