/*
 *  Microsoft Confidential
 *  Copyright (C) Microsoft Corporation 1991
 *  All Rights Reserved.
 *
 *
 *  PIFFNT.C
 *  User interface dialogs for GROUP_FNT
 *
 *  History:
 *  Created 04-Jan-1993 1:10pm by Jeff Parsons
 *  Changed 05-May-1993 5:10pm by Raymond Chen -- New Chicago-look preview
 *  Changed 12-Aug-1993 4:14pm by Raymond Chen -- Remove font inc and dec
 *
 *  All font dialog code taken from font*.c in vmdosapp, 01-Apr-93
 */

#include "shellprv.h"
#pragma hdrstop

#define REGSTR_MSDOSEMU_DISPLAYPARAMS TEXT("DisplayParams")

#define REGSTR_PATH_MSDOSEMU "Software\\Microsoft\\Windows\\CurrentVersion\\MS-DOS Emulation"

const TCHAR szWndPreviewClass[] = TEXT("WOAWinPreview");
const TCHAR szFontPreviewClass[] = TEXT("WOAFontPreview");

// The preview strings for bilingual dosbox. 
// We'll load this from our resource that will be properly
// localized. We'll give up if it fails and use above sample
// instead.

UINT cxScreen, cyScreen, dyChar, dyItem;

// Macro definitions that handle codepages 
//
#define OEMCharsetFromCP(cp) \
    ((cp)==CP_JPN? SHIFTJIS_CHARSET : ((cp)==CP_WANSUNG? HANGEUL_CHARSET : OEM_CHARSET))
/*
 * Font cache information.  Note that this cache, being in PIFMGR,
 * is now global, which will make support for per-VM font files/faces
 * more problematic, if we even decide that's an interesting feature.
 *
 */
DWORD   bpfdiStart[2] =  {  0  };    /* strage for the offset to cache */
UINT    cfdiCache[2];                   /* # used entries in fdi cache */
UINT    cfdiCacheActual[2];             /* Total # entries in fdi cache */
LPVOID lpCache = NULL;


/*
 * Owner-draw list box information.
 *
 */
HBITMAP hbmFont;                        /* Handle to "TrueType" logo */
DWORD   dwTimeCheck;
COLORREF clrChecksum;

HCURSOR hcursorWait;

#define MAXDIMENSTRING 80

/*
 * Initial font heights for TT fonts
 *
 * This is read from an INI file, so it must remain writeable.
 *
 * We don't try generating TT fonts below 12pt by default because
 * they just look crappy.  Frosting setup will put a different
 * table into place because Lucida Console looks good down to 4pt.
 *
 * On NT, Lucida Console is installed be default, though.
 *
 * The Frosting table is
 *      4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22
 */
WORD rgwInitialTtHeights[] = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22 };



/*
 * rgpnlPenalties -- Initialize penalty array to default values
 */
INT rgpnlPenalties[] =
        { 5000, 1000, 0, 1000, 5000, 1000, 0, 1000, 1 };


POINT ptNonAspectMin = { -1, -1 };

// Context-sensitive help ids

const static DWORD rgdwHelp[] = {
    IDC_FONTGRP,            IDH_COMM_GROUPBOX,
    IDC_RASTERFONTS,        IDH_DOS_AVAIL_FONTS,
    IDC_TTFONTS,            IDH_DOS_AVAIL_FONTS,
    IDC_BOTHFONTS,          IDH_DOS_AVAIL_FONTS,
    IDC_FONTSIZELBL,        IDH_DOS_FONT_SIZE,
    IDC_FONTSIZE,           IDH_DOS_FONT_SIZE,
    IDC_WNDPREVIEWLBL,      IDH_DOS_FONT_WINDOW_PREVIEW,
    IDC_FONTPREVIEWLBL,     IDH_DOS_FONT_FONT_PREVIEW,
    IDC_WNDPREVIEW,         IDH_DOS_FONT_WINDOW_PREVIEW,
    IDC_FONTPREVIEW,        IDH_DOS_FONT_FONT_PREVIEW,
    IDC_REALMODEDISABLE,    IDH_DOS_REALMODEPROPS,
    0, 0
};


BOOL_PTR CALLBACK DlgFntProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    PFNTINFO pfi = (PFNTINFO)GetWindowLongPtr(hDlg, DWLP_USER);

    switch (uMsg) 
    {
    case WM_INITDIALOG:
        // allocate dialog instance data
        if (NULL != (pfi = (PFNTINFO)LocalAlloc(LPTR, sizeof(FNTINFO)))) 
        {
            pfi->ppl = (PPROPLINK)((LPPROPSHEETPAGE)lParam)->lParam;
            SetWindowLongPtr(hDlg, DWLP_USER, (LPARAM)pfi);
            InitFntDlg(hDlg, pfi);
            break;
        }
        else
        {
            EndDialog(hDlg, FALSE);     // fail the dialog create
        }
        break;

    case WM_DESTROY:
        // free any allocations/resources inside the pfi first!
        if (pfi) 
        {
            if (pfi->hFontPreview)
            {
                DeleteObject(pfi->hFontPreview);
                pfi->hFontPreview = NULL;
            }
            // ok, NOW we can free the pfi
            EVAL(LocalFree(pfi) == NULL);
            SetWindowLongPtr(hDlg, DWLP_USER, 0);
        }
        break;

    HELP_CASES(rgdwHelp)                // Handle help messages

    case WM_COMMAND:
        if (LOWORD(lParam) == 0)
            break;                      // message not from a control

        switch (LOWORD(wParam))
        {
        case IDC_RASTERFONTS:
        case IDC_TTFONTS:
        case IDC_BOTHFONTS:

            /*
             * Rebuild the font list based on the user's selection of
             * which fonts to include/exclude.
             */
            pfi->fntProposed.flFnt &= ~FNT_BOTHFONTS;
            pfi->fntProposed.flFnt |= FNTFLAGSFROMID(wParam);
            CreateFontList(GetDlgItem(hDlg, IDC_FONTSIZE), TRUE, &pfi->fntProposed);
            PreviewUpdate(GetDlgItem(hDlg, IDC_FONTSIZE), pfi);

            if (HIWORD(wParam) == BN_CLICKED)
                SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0L);

            return FALSE;

        case IDC_FONTSIZE:

            if (HIWORD(wParam) == LBN_SELCHANGE)
            {
                PreviewUpdate(GetDlgItem(hDlg, IDC_FONTSIZE), pfi);
                SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0L);
                return TRUE;
            }
            if (HIWORD(wParam) == LBN_DBLCLK)
                ApplyFntDlg(hDlg, pfi);

            return FALSE;
        }
        break;

    case WM_NOTIFY:
        switch (((NMHDR *)lParam)->code) {
        case PSN_SETACTIVE:
            AdjustRealModeControls(pfi->ppl, hDlg);
            break;

        case PSN_KILLACTIVE:
            // This gives the current page a chance to validate itself
            // SetWindowLong(hDlg, DWL_MSGRESULT, ValidFntDlg(hDlg, pfi));
            break;

        case PSN_APPLY:
            // This happens on OK....
            ApplyFntDlg(hDlg, pfi);
            break;

        case PSN_RESET:
            // This happens on Cancel....
            break;
        }
        break;

    /*
     *  For WM_MEASUREITEM and WM_DRAWITEM, since there is only one
     *  owner-draw list box in the entire dialog box, we don't have
     *  to do a GetDlgItem to figure out who he is.
     */

    case WM_MEASUREITEM:
        // measure the owner draw listbox
        MeasureItemFontList((LPMEASUREITEMSTRUCT)lParam);
        break;

    case WM_DRAWITEM:
        DrawItemFontList(TRUE, (LPDRAWITEMSTRUCT)lParam);
        break;

    case WM_SYSCOLORCHANGE:
        UpdateTTBitmap();
        break;

    default:
        return FALSE;                   // return 0 when not processing
    }
    return TRUE;
}


/** InitFntDlg
 *
 *  Create the list of appropriate fonts.
 *
 *  This routine is broken out of FontDlgProc because it chew
 *  up lots of stack for the message buffer, and we don't want to
 *  eat that much stack on every entry to FontDlgProc.
 *
 *  Note that we must defer CreateFontList until WM_INITDIALOG
 *  time because it is not until then that we have a list box that
 *  we can shove the data into.
 */

void InitFntDlg(HWND hDlg, register PFNTINFO pfi)
{
    HWND hwndList;              /* The listbox of fonts */
    PPROPLINK ppl = pfi->ppl;
    WINDOWPLACEMENT wp;

    ASSERTTRUE(ppl->iSig == PROP_SIG);

    if (!PifMgr_GetProperties(ppl, MAKELP(0,GROUP_FNT),
                        &pfi->fntProposed, sizeof(pfi->fntProposed), GETPROPS_NONE)
        ||
        !PifMgr_GetProperties(ppl, MAKELP(0,GROUP_WIN),
                        &pfi->winOriginal, sizeof(pfi->winOriginal), GETPROPS_NONE)) {
        Warning(hDlg, IDS_QUERY_ERROR, MB_ICONEXCLAMATION | MB_OK);
        return;
    }

    /*
     * Set up instance variables for the window preview window.
     */
     
    /* Show preview maximized if window is maximized or restores to max'd.
     * Also if it is open restored and has no scrollbars.
     * (Determined by comparing the client window size against the cell
     * size and font size.)
     */

    pfi->fMax = FALSE;

    /*
     * Preload winOriginal with up-to-the-minute goodies, if we have
     * them.
     */

#define HasScrollbars(z) \
    (pfi->winOriginal.c##z##Cells * pfi->fntProposed.c##z##FontActual > \
     pfi->winOriginal.c##z##Client)

    if (ppl->hwndTty) {
        wp.length = sizeof(WINDOWPLACEMENT);
        VERIFYTRUE(GetWindowPlacement(ppl->hwndTty, &wp));

        // Convert/Copy to 16-bit structure
        pfi->winOriginal.wLength          = (WORD)wp.length;
        pfi->winOriginal.wShowFlags       = (WORD)wp.flags;
        pfi->winOriginal.wShowCmd         = (WORD)wp.showCmd;
        pfi->winOriginal.xMinimize        = (WORD)wp.ptMinPosition.x;
        pfi->winOriginal.yMinimize        = (WORD)wp.ptMinPosition.y;
        pfi->winOriginal.xMaximize        = (WORD)wp.ptMaxPosition.x;
        pfi->winOriginal.yMaximize        = (WORD)wp.ptMaxPosition.y;
        pfi->winOriginal.rcNormal.left    = (WORD)wp.rcNormalPosition.left;
        pfi->winOriginal.rcNormal.top     = (WORD)wp.rcNormalPosition.top;
        pfi->winOriginal.rcNormal.right   = (WORD)wp.rcNormalPosition.right;
        pfi->winOriginal.rcNormal.bottom  = (WORD)wp.rcNormalPosition.bottom;

        if (!IsIconic(ppl->hwndTty) &&
                !HasScrollbars(x) && !HasScrollbars(y)) {
            pfi->fMax = TRUE;
        }
    }

    if ((pfi->winOriginal.wShowCmd == SW_SHOWMAXIMIZED) ||
        (pfi->winOriginal.wShowFlags & WPF_RESTORETOMAXIMIZED)) {
        pfi->fMax = TRUE;
    }

    if (pfi->winOriginal.wShowCmd == SW_SHOWMAXIMIZED) {
        pfi->ptCorner.x = (LONG)pfi->winOriginal.xMaximize;
        pfi->ptCorner.y = (LONG)pfi->winOriginal.yMaximize;
    } else {
        if (pfi->winOriginal.rcNormal.left==0)
        {
            pfi->ptCorner.x = -1;
        }
        else
        {
            pfi->ptCorner.x = (LONG)pfi->winOriginal.rcNormal.left;
        }
        pfi->ptCorner.y = (LONG)pfi->winOriginal.rcNormal.top;
    }

    /*
     * First, check which fonts the user wants to see.
     *
     */
    CheckDlgButton(hDlg, IDFROMFNTFLAGS(pfi->fntProposed.flFnt), TRUE);

    hwndList = GetDlgItem(hDlg, IDC_FONTSIZE);
    // SendMessage(hwndList, WM_SETFONT, (WPARAM)GetStockObject(SYSTEM_FIXED_FONT), FALSE);

    if (CreateFontList(hwndList, TRUE, &pfi->fntProposed) == LB_ERR) {
        MemoryWarning(hDlg);
        EndDialog(hDlg, PtrToLong(BPFDI_CANCEL));    /* Get out of the dialog */
        return;
    }

    /* Initialize the preview windows */
    PreviewInit(hDlg, pfi);
    PreviewUpdate(GetDlgItem(hDlg, IDC_FONTSIZE), pfi);
}


void ApplyFntDlg(HWND hDlg, register PFNTINFO pfi)
{
    PPROPLINK ppl = pfi->ppl;

    ASSERTTRUE(ppl->iSig == PROP_SIG);

    if (!PifMgr_SetProperties(ppl, MAKELP(0,GROUP_FNT),
                        &pfi->fntProposed, sizeof(pfi->fntProposed), SETPROPS_NONE))
        Warning(hDlg, IDS_UPDATE_ERROR, MB_ICONEXCLAMATION | MB_OK);
    else
    if (ppl->hwndNotify) {
        ppl->flProp |= PROP_NOTIFY;
        PostMessage(ppl->hwndNotify, ppl->uMsgNotify, sizeof(pfi->fntProposed), (LPARAM)MAKELP(0,GROUP_FNT));
    }
}

/*
 * Retrieves the name of the font to use for true-type DOS box
 * in a window given a registry tree root.
 *
 * Entry:
 *
 * hkRoot      -> registry tree root to search
 * pszFaceSbcs -> LF_FACESIZE buffer for SBCS font
 * pszFaceDbcs -> LF_FACESIZE buffer for DBCS font (may be null)
 *
 * Exit:
 *
 * Buffers filled with new font names, or left unchanged if nothing
 * found in registry.
 *
 */

#define REGSTR_MSDOSEMU_FONT "Font"
#define REGSTR_MSDOSEMU_FONTDBCS "FontDBCS"

void GetDosBoxTtFontsHkA(HKEY hkRoot, LPSTR pszFaceSbcs, LPSTR pszFaceDbcs)
{
    static CHAR const szMsdosemu[] = REGSTR_PATH_MSDOSEMU;
    HKEY hk;
    DWORD cb;

    if (RegOpenKeyExA(hkRoot, szMsdosemu, 0, KEY_READ, &hk) == ERROR_SUCCESS)
    {
        static CHAR const szFont[] = REGSTR_MSDOSEMU_FONT;
        cb = LF_FACESIZE;
        RegQueryValueExA(hk, szFont, 0, 0, (LPBYTE)pszFaceSbcs, &cb);

        if (pszFaceDbcs)
        {
            static CHAR const szDbcsFont[] = REGSTR_MSDOSEMU_FONTDBCS;
            cb = LF_FACESIZE;
            RegQueryValueExA(hk, szDbcsFont, 0, 0, (LPBYTE)pszFaceDbcs, &cb);
        }
        RegCloseKey(hk);
    }
}

/*
 * Retrieves the name of the font to use for true-type DOS box
 * in a window.
 *
 * This routine consults the appropriate registry keys.
 *
 * The DOS box font comes first from HKLM, to establish  a
 * machine-wide default, but can in turn be overridden by
 * HKCU for each user to override.
 *
 * Entry:
 *
 * pszFaceSbcs -> LF_FACESIZE buffer for SBCS font
 * pszFaceDbcs -> LF_FACESIZE buffer for DBCS font (may be null)
 *
 * Exit:
 *
 * Buffers filled with new font names, or left unchange if nothing
 * found in registry.
 *
 */

void CoolGetDosBoxTtFontsA(LPSTR pszFaceSbcs, LPSTR pszFaceDbcs)
{
    GetDosBoxTtFontsHkA(HKEY_LOCAL_MACHINE, pszFaceSbcs, pszFaceDbcs);
    GetDosBoxTtFontsHkA(HKEY_CURRENT_USER, pszFaceSbcs, pszFaceDbcs);
}

/** BroadcastFontChange
 *
 *  HACK! for MS PowerPoint 4.0.  These wallys, for some reason, will go
 *  off and eat up reams of CPU time if they receive a WM_FONTCHANGE
 *  message that was *posted*.  But if the message is *sent*, they do
 *  the right thing.  The puzzling thing is that they never call
 *  InSendMessage(), so how do they know?  What's more, why do they care?
 *  This was true in 3.1 also.  What's their problem?
 *
 *  The problem is that sending a broadcast risks deadlock city; see the
 *  various hacks in winoldap for places where DDE broadcasting is bypassed.
 *  In addition, since BroadcastFontChange is also called during the WEP,
 *  multi-threaded apps will deadlock if we SendMessage back to a window
 *  on a different thread in the app, because Kernel takes a process
 *  critical section during DLL unload.
 *
 *  So if PowerPig is running, we just don't tell anybody that we dorked
 *  with the fonts.
 *
 *  Returns:
 *
 *      None.
 *
 */

void BroadcastFontChange(void)
{
    if (!GetModuleHandle(szPP4)) {
        PostMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0L);
    }
}

/** LoadGlobalFontData
 *
 *  Get the name of the DOS box raster font and load it.
 *
 *  Get the name of the TT font.  (CheckDisplayParameters needs this.)
 *
 *  Check the display parameters.
 *
 *  Initialize the fdi cache.  The cache remains in GlobalLock'd
 *  memory throughout its lifetime.  This is not a problem because
 *  we are guaranteed to be in protected mode.
 *
 *  We also load things necessary for the font combo/list boxes.
 *
 *  And compute the height of the owner-draw list box item.
 *
 *  Returns:
 *
 *      TRUE on success.  In which case the FDI cache and hbmFont
 *      are ready to use.
 *
 *      FALSE on failure.  In which case there is insufficient memory
 *      to complete the operation.
 */

typedef void (WINAPI *LPFNGDBTF)(LPTSTR, LPTSTR); /* GetDosBoxTtFonts */

BOOL LoadGlobalFontData(void)
{
    HDC hDC;
    TEXTMETRIC tm;
    TCHAR szBuffer[MAXPATHNAME];

    cxScreen = GetSystemMetrics(SM_CXSCREEN);
    cyScreen = GetSystemMetrics(SM_CYSCREEN);

    /*
     * Get the system char size and save it away for later use.
     */
    hDC = GetDC(NULL);
    SelectObject(hDC, GetStockObject(SYSTEM_FONT));
    GetTextMetrics(hDC, &tm);
    ReleaseDC(NULL, hDC);

    dyChar = tm.tmHeight + tm.tmExternalLeading;
    dyItem = max(tm.tmHeight, DY_TTBITMAP);

    /*
     * Chicago's AddFontResource looks in the FONTS directory first, which
     * is great because it saves us the trouble of doing goofy disk access
     * optimizations.
     */
    GetPrivateProfileString(sz386EnhSection, szWOAFontKey,
                            c_szNULL, szBuffer, ARRAYSIZE(szBuffer), szSystemINI);
    if (szBuffer[0] && AddFontResource(szBuffer)) {
        BroadcastFontChange();
    }

    /*
     * Add DBCS native font if it is present.
     */
    GetPrivateProfileString(sz386EnhSection, szWOADBCSFontKey,
                            c_szNULL, szBuffer, ARRAYSIZE(szBuffer), szSystemINI);
    if (szBuffer[0] && AddFontResource(szBuffer)) {
        BroadcastFontChange();
    }

    /*
     * Load default TT font name(s) and TT cache section names from resource
     */
    LoadStringA(g_hinst, IDS_TTFACENAME_SBCS, szTTFaceName[0], ARRAYSIZE(szTTFaceName[0]));
    LoadString(g_hinst,IDS_TTCACHESEC_SBCS, szTTCacheSection[0], ARRAYSIZE(szTTCacheSection[0]));

    if (IsBilingualCP(g_uCodePage))
    {
        LoadStringA(g_hinst, IDS_TTFACENAME_DBCS, szTTFaceName[1], ARRAYSIZE(szTTFaceName[1]));
        LoadString(g_hinst, IDS_TTCACHESEC_DBCS, szTTCacheSection[1], ARRAYSIZE(szTTCacheSection[1]));
    }        

    CoolGetDosBoxTtFontsA(szTTFaceName[0], szTTFaceName[1]);

    CheckDisplayParameters();

    // alloc needed # of cache
    //
    lpCache = (LPVOID)LocalAlloc(LPTR,
                    FDI_TABLE_START * sizeof(FONTDIMENINFO) * (IsBilingualCP(g_uCodePage)? 2:1));
                         
    if (!lpCache)
        return FALSE;

    hcursorWait = LoadCursor(NULL, IDC_WAIT);

    UpdateTTBitmap();
    if (!hbmFont)
        goto E0;

    // set initial value of # of cache entries which depends on whether we have
    // two codepage to handle
    //
    cfdiCacheActual[0] = FDI_TABLE_START;

    if (IsBilingualCP(g_uCodePage))
    {
        cfdiCacheActual[1] = FDI_TABLE_START;
        bpfdiStart[1] += FDI_TABLE_START;
    }

    FontSelInit();

    return TRUE;

E0: 
    EVAL(LocalFree(lpCache) == NULL);

    return FALSE;
}



void FreeGlobalFontData()
{
    TCHAR szBuffer[MAXPATHNAME] = {0};

    if (hbmFont)
        DeleteObject(hbmFont);

    EVAL(LocalFree(lpCache) == NULL);



    GetPrivateProfileString(sz386EnhSection, szWOAFontKey,
                            c_szNULL, szBuffer, ARRAYSIZE(szBuffer), szSystemINI);
    if (*szBuffer) {
        if (RemoveFontResource(szBuffer)) {
            BroadcastFontChange();
        }
    }
    GetPrivateProfileString(sz386EnhSection, szWOADBCSFontKey,
                            c_szNULL, szBuffer, ARRAYSIZE(szBuffer), szSystemINI);
    if (*szBuffer) {
        if (RemoveFontResource(szBuffer)) {
            BroadcastFontChange();
        }
    }
}


BOOL LoadGlobalFontEditData()
{
    WNDCLASS wc;

    // Set up the window preview class for piffnt.c

    wc.style         = 0L;
    wc.lpfnWndProc   = WndPreviewWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = sizeof(PFNTINFO);
    wc.hInstance     = g_hinst;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szWndPreviewClass;

    // Don't go through RegisterClassD because we manually unregister
    // this class ourselves.
    if (!RealRegisterClass(&wc))
        return FALSE;

    // Set up the font preview class for piffnt.c

    wc.style         = 0L;
    wc.lpfnWndProc   = FontPreviewWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = sizeof(PFNTINFO);
    wc.hInstance     = g_hinst;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szFontPreviewClass;

    // Don't go through RegisterClassD because we manually unregister
    // this class ourselves.
    if (!RealRegisterClass(&wc))
        return FALSE;

    return TRUE;
}


void FreeGlobalFontEditData()
{
    UnregisterClass(szWndPreviewClass, g_hinst);
    UnregisterClass(szFontPreviewClass, g_hinst);
}


/*
 *  Make sure that the display parameters have not changed, including
 *  the name of the TT font(s).
 *
 *  If they have, we BLAST OUR CACHE since it is no longer any good.
 *
 *  Entry:
 *      szTTFaceName contains the name of the TrueType font to use.
 *
 *  Returns:
 *      None.
 */

void CheckDisplayParameters(void)
{
    HDC         hIC;
    HKEY        hk;
    DISPLAYPARAMETERS dpTrue, dpStored;

    hIC = CreateIC(szDisplay, 0, 0, 0);

    if (!hIC) {
        /*
         * If things are really screwy, stay conservative and assume
         * that all is well.
         */
        return;
    }

    dpTrue.dpHorzSize   = GetDeviceCaps(hIC, HORZSIZE);
    dpTrue.dpVertSize   = GetDeviceCaps(hIC, VERTSIZE);
    dpTrue.dpHorzRes    = GetDeviceCaps(hIC, HORZRES);
    dpTrue.dpVertRes    = GetDeviceCaps(hIC, VERTRES);
    dpTrue.dpLogPixelsX = GetDeviceCaps(hIC, LOGPIXELSX);
    dpTrue.dpLogPixelsY = GetDeviceCaps(hIC, LOGPIXELSY);
    dpTrue.dpAspectX    = GetDeviceCaps(hIC, ASPECTX);
    dpTrue.dpAspectY    = GetDeviceCaps(hIC, ASPECTY);
    dpTrue.dpBitsPerPixel = GetDeviceCaps(hIC, BITSPIXEL);
    DeleteDC(hIC);

    /*
     *  Since szTTFaceName is pre-initialized to "Courier New" padded
     *  with nulls, we can rely on the garbage after the end of the
     *  string always to be the same, so that a pure memory comparison
     *  will work.
     */
    MultiByteToWideChar(CP_ACP, 0, szTTFaceName[0], -1, dpTrue.szTTFace[0], ARRAYSIZE(dpTrue.szTTFace[0]));
    if (IsBilingualCP(g_uCodePage))
        MultiByteToWideChar(CP_ACP, 0, szTTFaceName[1], -1, dpTrue.szTTFace[1], ARRAYSIZE(dpTrue.szTTFace[1]));

    /*
     *  We must store the dimension information in the registry because
     *  the install program for Omar Sharif Bridge will ERASE! your
     *  SYSTEM.INI if it contains a line greater than 78 characters.
     *  (I am not making this up.  How could I?)
     */

    if (RegCreateKey(HKEY_LOCAL_MACHINE, TEXT(REGSTR_PATH_MSDOSEMU), &hk) == 0) {
        DWORD cb = sizeof(DISPLAYPARAMETERS);
        if (SHQueryValueEx(hk, REGSTR_MSDOSEMU_DISPLAYPARAMS, 0, 0, (LPVOID)&dpStored, &cb) != 0 || cb != sizeof(DISPLAYPARAMETERS) || IsBufferDifferent(&dpTrue, &dpStored, sizeof(DISPLAYPARAMETERS))) {
            /*
             * Not much we can do if the write fails, so don't check.
             */
            VERIFYTRUE(RegSetValueEx(hk, REGSTR_MSDOSEMU_DISPLAYPARAMS, 0, REG_BINARY, (LPVOID)&dpTrue, cb) == 0);

            /* Blast the font dimension cache */
            WritePrivateProfileString(szTTCacheSection[1], NULL, NULL, szSystemINI);
            if (IsBilingualCP(g_uCodePage))
                WritePrivateProfileString(szTTCacheSection[0], NULL, NULL, szSystemINI);
        }
        VERIFYTRUE(RegCloseKey(hk) == 0);
    } else {
        /*
         *  Couldn't access registry.  Oh well.
         */
    }

}

/*
 *  When the dialog box is created, we create the Window
 *  Preview child window, as well as the Font Preview window.
 *
 *  The creation is deferred until the actual dialog box creation
 *  because the size and shape of the Window Preview window depends
 *  on the current video driver.
 */

void PreviewInit(HWND hDlg, PFNTINFO pfi)
{
    HWND hwnd;
    RECT rectLabel, rcPreview;

    /*
     * Compute the size of our preview window.
     *
     *  The top is aligned with the top of IDC_WNDPREVIEWLBL,
     *          minus a top margin of 3/2 dyChar.
     *  The left edge is aligned with the left edge of IDC_WNDPREVIEWLBL.
     *  The maximum width is the width of IDC_WNDPREVIEWLBL.
     *  The bottom edge can go as far down as the bottom of the dialog,
     *          minus a bottom margin of 3/2 dyChar.
     *  And the shape of the preview window is determined by the screen
     *          dimensions.
     *
     * We make the preview window as large as possible, given these
     * constraints.
     *
     */
    GetWindowRect(GetDlgItem(hDlg, IDC_WNDPREVIEWLBL), &rectLabel);
    ScreenToClient(hDlg, (LPPOINT)&rectLabel);
    ScreenToClient(hDlg, (LPPOINT)&rectLabel.right);

    /*
     * This GetWindowRect/ScreenToClient sets rcPreview.top.
     */
    GetWindowRect(GetDlgItem(hDlg, IDC_WNDPREVIEWLBL), &rcPreview);
    ScreenToClient(hDlg, (LPPOINT)&rcPreview);

    /*
     * Compute height based on width.
     */
    rcPreview.top += 3 * dyChar / 2;
    rcPreview.left = rectLabel.left;
    rcPreview.right = rectLabel.right - rectLabel.left;
    rcPreview.bottom = AspectScale(cyScreen, cxScreen, rcPreview.right);

    /*
     * Phew.  Now we can create the preview window.
     */
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        szWndPreviewClass, NULL,
        WS_CHILD | WS_VISIBLE,
        rcPreview.left, rcPreview.top,
        rcPreview.right, rcPreview.bottom,
        hDlg, (HMENU)IDC_WNDPREVIEW, g_hinst, NULL);

    if (hwnd)
        SetWindowLongPtr(hwnd, 0, (LONG_PTR)pfi);

    /*
     * Compute the size of the font preview.  This is easier.
     */
    GetWindowRect(GetDlgItem(hDlg, IDC_FONTPREVIEWLBL), &rectLabel);
    ScreenToClient(hDlg, (LPPOINT)&rectLabel.left);
    ScreenToClient(hDlg, (LPPOINT)&rectLabel.right);

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        szFontPreviewClass, NULL,
        WS_CHILD | WS_VISIBLE,
        rectLabel.left,
        rectLabel.top + 3 * dyChar / 2,
        rectLabel.right - rectLabel.left,
        rcPreview.bottom,
        hDlg, (HMENU)IDC_FONTPREVIEW, g_hinst, NULL);

    if (hwnd)
        SetWindowLongPtr(hwnd, 0, (LONG_PTR)pfi);
}


/*  PreviewUpdate
 *
 *  Does the preview of the selected font.
 */

void PreviewUpdate(HWND hwndList, PFNTINFO pfi)
{
    HWND hDlg;
    BPFDI bpfdi;

    /* Delete the old font if necessary */
    if (pfi->hFontPreview)
    {
        DeleteObject(pfi->hFontPreview);
        pfi->hFontPreview = NULL;
    }

    /* When we select a font, we do the font preview by setting it
     * into the appropriate list box
     */
    bpfdi = (BPFDI)GetFont(hwndList, TRUE, pfi);
    if (IsSpecialBpfdi(bpfdi))
        return;

    /* Update our internal font structure so that preview window
     * will actually change
     */
    pfi->bpfdi = bpfdi;
    SetFont(&pfi->fntProposed, bpfdi);

    /* Make the new font */
    pfi->hFontPreview = CreateFontFromBpfdi(bpfdi, pfi);

    /* Force the preview windows to repaint */
    hDlg = GetParent(hwndList);
    InvalidateRect(GetDlgItem(hDlg, IDC_WNDPREVIEW), NULL, TRUE);
    InvalidateRect(GetDlgItem(hDlg, IDC_FONTPREVIEW), NULL, TRUE);
}


/*  WndPreviewWndProc
 *
 *  Handles the window preview window.
 */

LRESULT WndPreviewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_PAINT:
        WndPreviewPaint(GetParent(hwnd), hwnd);
        break;

    case WM_HELP:       // Handles title bar help button message
        WinHelp(hwnd, NULL, HELP_CONTEXTPOPUP, IDH_DOS_FONT_WINDOW_PREVIEW);
        break;

    case WM_RBUTTONUP:
    case WM_NCRBUTTONUP: // Equivalent of WM_CONTEXTMENU
        OnWmContextMenu((WPARAM)hwnd, &rgdwHelp[0]);
        break;

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return 0;
}

/*  Swiped from Control Panel / Metrics.
 *
 *  Draws the frame *and* modifies the rectangle to contain the
 *  shrunk coordinates.
 */
void _DrawFrame(HDC hdc, int clr, LPRECT lprc, int cx, int cy)
{
    HBRUSH hbr;
    RECT rcT;

    CopyRect(&rcT, lprc);
    hbr = SelectObject(hdc, GetSysColorBrush(clr));

    /* Left */
    PatBlt(hdc, rcT.left, rcT.top, cx, rcT.bottom-rcT.top, PATCOPY);
    rcT.left += cx;

    /* Top */
    PatBlt(hdc, rcT.left, rcT.top, rcT.right-rcT.left, cy, PATCOPY);
    rcT.top += cy;

    /* Right */
    rcT.right -= cx;
    PatBlt(hdc, rcT.right, rcT.top, cx, rcT.bottom-rcT.top, PATCOPY);

    /* Bottom */
    rcT.bottom -= cy;
    PatBlt(hdc, rcT.left, rcT.bottom, rcT.right-rcT.left, cy, PATCOPY);

    SelectObject(hdc, hbr);
    CopyRect(lprc, &rcT);
}


/*  WndPreviewPaint
 *
 *  Paints the window preview window.  This is called from its
 *  paint message handler.
 *
 */

void WndPreviewPaint(HWND hDlg, HWND hwnd)
{
    PPROPLINK ppl;
    PFNTINFO pfi;
    RECT rcPreview;
    RECT rcWin;
    RECT rcClient;
    RECT rcT;
    POINT ptButton;
#define cxButton    ptButton.x
#define cyButton    ptButton.y
    POINT ptCorner;
    POINT ptFrame;
#define cxFrame    ptFrame.x
#define cyFrame    ptFrame.y
    BPFDI bpfdi;
    int cxBorder, cyBorder;
    int dyToolbar;
    PAINTSTRUCT ps;
    BOOL bCenter;

    BeginPaint(hwnd, &ps);

    pfi = (PFNTINFO)GetWindowLongPtr(hwnd, 0);

    ppl = pfi->ppl;
    ASSERTTRUE(ppl->iSig == PROP_SIG);

    bpfdi = pfi->bpfdi;

    /* If we don't have a font, get out */
    if (!pfi->hFontPreview)
        return;

    /* Get the width of the preview "screen" */
    GetClientRect(hwnd, &rcPreview);

    /* Figure out how large we would be as a result of the change.
     * This isn't perfect, but it'll probably be close enough.
     * (Imperfection if we chose AUTO as the font.)
     */

    /* Assume maximized */
    rcClient.left = rcClient.top = 0;
    if (pfi->winOriginal.cxCells) {
        rcClient.right = pfi->winOriginal.cxCells * bpfdi->fdiWidthActual;
    } else {
        rcClient.right = 80 * bpfdi->fdiWidthActual;
    }

    if (pfi->winOriginal.cyCells) {
        rcClient.bottom = pfi->winOriginal.cyCells * bpfdi->fdiHeightActual;
    } else {
        PROPVID vid;

        // set default value
        rcClient.bottom = 25 * bpfdi->fdiHeightActual;

        // now see if there is a value in the pif file for size of window...
        if (PifMgr_GetProperties(ppl, MAKELP(0,GROUP_VID),
                        &vid, sizeof(vid), GETPROPS_NONE))
        {
            if (vid.cScreenLines > 0)
                rcClient.bottom = vid.cScreenLines * bpfdi->fdiHeightActual;

        }
    }
    if (!pfi->fMax && pfi->winOriginal.cxClient && pfi->winOriginal.cyClient) {
        /* Shrink down to window actual */
        if (rcClient.right > (int)pfi->winOriginal.cxClient)
            rcClient.right = (int)pfi->winOriginal.cxClient;
        if (rcClient.bottom > (int)pfi->winOriginal.cyClient)
            rcClient.bottom = (int)pfi->winOriginal.cyClient;
    }

    /* Get some more metrics */
    cxBorder = GetSystemMetrics(SM_CXBORDER);
    cyBorder = GetSystemMetrics(SM_CYBORDER);

    cxButton = GetSystemMetrics(SM_CXSIZE);
    cyButton = GetSystemMetrics(SM_CYSIZE);
//  cyButton *= 2;                      /* Double the height for "looks" */

    cxFrame = GetSystemMetrics(SM_CXFRAME);
    cyFrame = GetSystemMetrics(SM_CYFRAME);

    /* FLAG DAY!  Convert everything from desktop coordinates to
     * aspect ratio-scaled preview coordinates
     *
     * Do **not** convert cxBorder and cyBorder!
     *
     * ptCorner must not be modified in-place since its value is used at
     * the next go-round.
     *
     * After translation, cxFrame and cyFrame are adjusted so that the
     * cxBorder counts against them.  This allows for users who set
     * really wide frames, but doesn't penalize the users who have
     * narrow frames.
     */

    ptCorner = pfi->ptCorner;
    bCenter = (ptCorner.x == -1);
    AspectPoint(&rcPreview, &ptCorner);
    AspectPoint(&rcPreview, &ptFrame);
    AspectRect(&rcPreview, &rcClient);
    AspectPoint(&rcPreview, &ptButton);

    /*
     * The height of a toolbar is hard-coded at 30 pixels.
     */
    if (pfi->winOriginal.flWin & WIN_TOOLBAR) {
        dyToolbar = (int)AspectScale(rcPreview.bottom, cyScreen, 30);
    } else {
        dyToolbar = 0;
    }

    /* Make sure the buttons are nonzero in dimension */
    if (cxButton == 0) cxButton = 1;
    if (cyButton == 0) cyButton = 1;

    /*
     * Don't penalize people who have thin frames.
     */
    if (cxFrame < cxBorder) cxFrame = cxBorder;
    if (cyFrame < cyBorder) cyFrame = cyBorder;

    /*
     * Convert from client rectangle back to window rectangle.
     *
     * We must do this *AFTER* the flag day, because we need to use the
     * post-flag day cxBorder and cyBorder.
     */

    /* Put a (scaled-down) toolbar into place.  We'll expand the client
     * region to accomodate it.  (We'll subtract the toolbar off before
     * painting the client region.)
     */
    rcClient.bottom += dyToolbar;

    /* Shove the client region down to make room for the caption. */
    OffsetRect(&rcClient, 0, cyButton);

    rcWin = rcClient;
    rcWin.top = 0;
    InflateRect(&rcWin, cxFrame, cyFrame);

    /*
     * Now put it in the proper position on the (shrunk-down) desktop.
     * We cannot do this until rcWin's value is finalized.
     */
    if (bCenter)
    {
        ptCorner.x = ((rcPreview.right - rcPreview.left) -
                       (rcWin.right  - rcWin.left)
                     ) / 2;
        if (ptCorner.x < 0)
            ptCorner.x = 0;

        ptCorner.y = ((rcPreview.bottom - rcPreview.top) -
                       (rcWin.bottom  - rcWin.top)
                     ) / 5;
        if (ptCorner.y < 0)
            ptCorner.y = 0;

    }
    OffsetRect(&rcWin, ptCorner.x, ptCorner.y);
    OffsetRect(&rcClient, ptCorner.x, ptCorner.y);

    /* It's party time! */

    /* The outer border */
    DrawEdge(ps.hdc, &rcWin, BDR_RAISEDINNER, BF_RECT | BF_ADJUST);

    /* The sizing frame */
    _DrawFrame(ps.hdc, COLOR_ACTIVEBORDER,
                    &rcWin, cxFrame - cxBorder, cyFrame - cyBorder);

    /* rcWin has now shrunk to its inner edge */

    /* Move its bottom edge upwards to meet the top of the client region.
     * This turns rcWin into the caption area.
     */
    rcWin.bottom = rcClient.top;
    FillRect(ps.hdc, &rcWin, (HBRUSH)(COLOR_ACTIVECAPTION+1));

    /* Next comes the toolbar */
    rcT= rcClient;
    rcT.bottom = rcT.top + dyToolbar;
    FillRect(ps.hdc, &rcT, (HBRUSH)(COLOR_BTNFACE+1));

    /* Next, draw the client region */
    rcClient.top += dyToolbar;
    DrawEdge(ps.hdc, &rcClient, BDR_SUNKENOUTER, BF_RECT | BF_ADJUST);
    FillRect(ps.hdc, &rcClient, (HBRUSH)GetStockObject(BLACK_BRUSH));

    /*
     * Now draw the three caption buttons.
     */

    /*
     * The system menu.
     */
    rcT = rcWin;
    rcT.right = rcT.left + cxButton;
  //DrawFrameControl(ps.hdc, &rcT, DFC_SYSMENU, DFCS_SYSMENUMAIN);
    DrawFrameControl(ps.hdc, &rcT, DFC_CAPTION, DFCS_CAPTIONCLOSE);

    /*
     * The maximize menu.
     */
    rcWin.left = rcWin.right - cxButton;
  //DrawFrameControl(ps.hdc, &rcWin, DFC_SIZE, DFCS_SIZEMAX);
    DrawFrameControl(ps.hdc, &rcWin, DFC_CAPTION, DFCS_CAPTIONMAX);

    /*
     * The minimize menu.
     */
    rcWin.left -= cxButton;
    rcWin.right -= cxButton;
  //DrawFrameControl(ps.hdc, &rcWin, DFC_SIZE, DFCS_SIZEMIN);
    DrawFrameControl(ps.hdc, &rcWin, DFC_CAPTION, DFCS_CAPTIONMIN);

    EndPaint(hwnd, &ps);
}
#undef cxButton
#undef cyButton

#undef cxFrame
#undef cyFrame

LRESULT FontPreviewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    RECT rect;
    PFNTINFO pfi;
    PAINTSTRUCT ps;

    switch (uMsg)
    {
    case WM_PAINT:
        BeginPaint(hwnd, &ps);

        pfi = (PFNTINFO)GetWindowLongPtr(hwnd, 0);

        /* Draw the font sample */
        SelectObject(ps.hdc, pfi->hFontPreview);
        SetTextColor(ps.hdc, RGB(192, 192, 192));
        SetBkColor(ps.hdc, RGB(0, 0, 0));
        GetClientRect(hwnd, &rect);
        InflateRect(&rect, -2, -2);

        {
            TCHAR szPreviewText[300];
            LoadString(g_hinst, IsBilingualCP(pfi->fntProposed.wCurrentCP) ? IDS_PREVIEWTEXT_BILNG : IDS_PREVIEWTEXT, szPreviewText, ARRAYSIZE(szPreviewText));
            // load a sample for their native codepage
            DrawText(ps.hdc, szPreviewText, -1, &rect, 0);
        }

        EndPaint(hwnd, &ps);
        break;

    case WM_HELP:       // Handles title bar help button message
        WinHelp(hwnd, NULL, HELP_CONTEXTPOPUP, IDH_DOS_FONT_FONT_PREVIEW);
        break;

    case WM_RBUTTONUP:
    case WM_NCRBUTTONUP: // Equivalent of WM_CONTEXTMENU
        OnWmContextMenu((WPARAM)hwnd, &rgdwHelp[0]);
        break;

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return 0;
}


/*
 *  Loads the dialog control hwndList with all available font
 *  dimensions for raster fonts, and a selected collection of
 *  dimensions for TrueType fonts.
 *
 *  The reference data for each control is an index into lpfniCache.
 *
 *  The hourglass cursor is displayed during the font list build.
 *
 *  Entry:
 *      hwndList == handle to listbox or combo box to fill
 *      fListBox == TRUE if hwndList is a listbox, FALSE if a combo box
 *      lpFnt    -> PROPFNT structure
 *
 *      If HIWORD(lpFnt) is NULL, then LOWORD(lpFnt) is used as an hProps
 *      to get obtain property info for that handle.
 *
 *  Returns:
 *      >= 0 on success, indicating the current selection.
 *      In which case the FDI cache is valid and hwndList has been filled
 *      with font information, and the currently-selected font has been
 *      made the current selection.
 *
 *      LB_ERR/CB_ERR on failure.  The list box hwndList is left in an
 *      indeterminate state.
 */

int WINAPI CreateFontList(HWND hwndList, BOOL fListBox, LPPROPFNT lpFnt)
{
    DWORD   dwIndex;
    HCURSOR hcursor;
    PROPFNT fntTemp;
    int     iReturn = LB_ERR;
    TCHAR   szBuf[MAXDIMENSTRING];

    if (IS_INTRESOURCE(lpFnt))
    {
        if (!PifMgr_GetProperties(lpFnt, MAKELP(0,GROUP_FNT),
                           &fntTemp, sizeof(fntTemp), GETPROPS_NONE))
            goto Exit2;

        lpFnt = &fntTemp;
    }

    /*
     * Put up an hourglass while the font list build is taking place,
     * since it might take a long time if we have to re-rasterize
     * TrueType fonts.
     *
     * NOTE!  That we do not do a ShowCursor.  Why?
     *
     *  If the user is on a mouseless system, then he can't access his
     *  toolbar, and hence the only time this code can get called is
     *  during the creation of the font selection dialog box.  In which
     *  case, DialogBox has already done a ShowCursor for us.
     *
     */
    hcursor = SetCursor(hcursorWait);

    /*
     * Initialize the list box.
     */
    if (hwndList) {
        SendMessage(hwndList, WM_SETREDRAW, FALSE, 0L);
        SendMessage(hwndList, fListBox ? LB_RESETCONTENT : CB_RESETCONTENT, 0, 0L);
    }

    /*
     * Add the fonts.
     */
    if ((lpFnt->flFnt & FNT_RASTERFONTS) &&
        !AddRasterFontsToFontListA(hwndList, fListBox,
                                  lpFnt->achRasterFaceName, lpFnt->wCurrentCP))
        goto Exit;

    if ((lpFnt->flFnt & FNT_TTFONTS) &&
        !AddTrueTypeFontsToFontListA(hwndList, fListBox,
                                  lpFnt->achTTFaceName, lpFnt->wCurrentCP))
        goto Exit;

    /*
     * And the magical "Auto" font size.
     */

    /*
     * Beyond this point, success is assured, so at the very least,
     * DON'T return LB_ERR;  we may optionally set the return code to
     * the current selection, below, too...
     */
    iReturn = 0;

    if (hwndList) {
        /*
         * No error checking here because if anything fails, then the
         * end result will be merely that the "Auto" option either
         * (1) exists but is invisible, or (2) doesn't appear at all.
         */
        LoadString(g_hinst, IDS_AUTO, szBuf, ARRAYSIZE(szBuf));
        dwIndex = lcbInsertString(hwndList, fListBox, szBuf, 0);
        lcbSetItemDataPair(hwndList, fListBox, dwIndex, BPFDI_AUTO, 0);

        /*
         * Make yet another pass through the list to find the current
         * font and select it.  Thanks to an intentional flakiness
         * in USER, we can't do this check at the point that the
         * font is added, because the selection does not move with the
         * item when a new item is inserted above the selection.
         *
         * Bleah.
         */
        if (!MatchCurrentFont(hwndList, fListBox, lpFnt)) {
            /*
             * If no font matched the current font, and we are a list box,
             * then make the first font the current selection.
             *
             * We don't want to make any default selection if we are a
             * combo box, because that would make the user think that the
             * current font was something it wasn't.
             */
            if (fListBox) {
                /*
                 * SORTING-SENSITIVE!  This assumes that "Auto" is at the top
                 * of the list.
                 */
                lcbSetCurSel(hwndList, TRUE, 0);
                lpFnt->flFnt |= FNT_AUTOSIZE;
            }
        }
        SendMessage(hwndList, WM_SETREDRAW, TRUE, 0L);

        iReturn = lcbGetCurSel(hwndList, fListBox);
    }
Exit:
    /*
     * Reset the mouse cursor.
     */
    SetCursor(hcursor);

Exit2:
    return iReturn;
}


/** UpdateTTBitmap
 *
 *  Recompute the colors for the TrueType bitmap hbmFont.
 *
 *  Since we may receive this several times for a single WM_SYSCOLORCHANGE,
 *  we update our colors under the following conditions:
 *
 *      1. More than one second has elapsed since the previous call, or
 *      2. A crude checksum fails.
 *
 *  Entry:
 *      None.
 *
 *  Returns:
 *      hbmFont recomputed.
 */

VOID WINAPI UpdateTTBitmap(void)
{
    COLORREF clr;

    /*
     *  Note that the checksum should not be a symmetric function,
     *  because a common color alteration is to exchange or permute
     *  the colors.
     */
    clr = +  GetSysColor(COLOR_BTNTEXT)
          -  GetSysColor(COLOR_BTNSHADOW)
          + (GetSysColor(COLOR_BTNFACE) ^ 1)
          - (GetSysColor(COLOR_BTNHIGHLIGHT) ^ 2)
          ^  GetSysColor(COLOR_WINDOW);

    if (!hbmFont || clr != clrChecksum || GetTickCount() - dwTimeCheck < 1000) {
        clrChecksum = clr;
        dwTimeCheck = GetTickCount();
        if (hbmFont) DeleteObject(hbmFont);
        hbmFont = CreateMappedBitmap(g_hinst, IDB_TTBITMAP, 0, NULL, 0);
    }
}


/** DrawItemFontList
 *
 *  Answer the WM_DRAWITEM message sent from the font list box or
 *  font combo box.
 *
 *  This code was originally lifted from FONT.C in sdk\commdlg.
 *
 *  See fontutil.h for an explanation of the \1 hack.
 *
 *  Entry:
 *      fListBox    =  TRUE if the item is a list box, FALSE if a combo box
 *      lpdis       -> DRAWITEMSTRUCT describing object to be drawn
 *
 *  Returns:
 *      None.
 *
 *      The object is drawn.
 */

#define cTabsList 3

typedef struct DIFLINFO {
    LPTSTR       di_lpsz;
    PINT        di_pTabs;
} DIFLINFO, *LPDIFLINFO;

#define lpdi ((LPDIFLINFO)lp)
BOOL CALLBACK diflGrayStringProc(HDC hdc, LPARAM lp, int cch)
{
    return (BOOL)TabbedTextOut(hdc, 0, 0,
                  lpdi->di_lpsz, lstrlen(lpdi->di_lpsz),
                  cTabsList, lpdi->di_pTabs, 0);

}
#undef lpdi

VOID WINAPI DrawItemFontList(BOOL fListBox, const LPDRAWITEMSTRUCT lpdis)
{
    HDC     hDC, hdcMem;
    DWORD   rgbBack, rgbText;
    int     iColorBack;
    COLORREF clrText;
    COLORREF clrBack;
    TCHAR    szDimen[MAXDIMENSTRING];
    HBITMAP hOld;
    int     dy;
    DIFLINFO di;
    static int rgTabsList[cTabsList] = {0, 0, 0};
    static int rgTabsCombo[cTabsList] = {0, 0, 0};
#define lpsz di.di_lpsz
#define pTabs di.di_pTabs

    if ((int)lpdis->itemID < 0)
        return;

    hDC = lpdis->hDC;

    if (lpdis->itemAction & ODA_FOCUS) {
        if (lpdis->itemState & ODS_SELECTED) {
            DrawFocusRect(hDC, &lpdis->rcItem);
        }
    } else {
        if (lpdis->itemState & ODS_SELECTED) {
            clrBack = GetSysColor(iColorBack = COLOR_HIGHLIGHT);
            clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
        } else {
            clrBack = GetSysColor(iColorBack = COLOR_WINDOW);
            clrText = GetSysColor(IsWindowEnabled(lpdis->hwndItem) ?
                                        COLOR_WINDOWTEXT : COLOR_GRAYTEXT);
        }
        rgbText = SetTextColor(hDC, clrText);
        rgbBack = SetBkColor(hDC, clrBack);

        // draw selection background
        FillRect(hDC, &lpdis->rcItem, (HBRUSH)UIntToPtr((iColorBack + 1)));

        // get the string
        SendMessage(lpdis->hwndItem, fListBox ? LB_GETTEXT : CB_GETLBTEXT, lpdis->itemID, (LPARAM)(LPTSTR)szDimen);

        lpsz = szDimen;
        if (szDimen[0] == TEXT('\1'))   // hack for "Auto" string
            lpsz++;

        if (fListBox)
            pTabs = rgTabsList;
        else
            pTabs = rgTabsCombo;

        if (pTabs[0] == 0) {            /* Never seen this font before */
            /* Assumes GetTextExtent(hDC, ANSI_TIMES, 1) < 2 * dxChar */
            SIZE sSize;
            GetTextExtentPoint32(hDC, szZero, 1, &sSize); // size of '0'
            /* A negative # for tab stop right aligns the tabs... */
            pTabs[0] = -sSize.cx * 3;
            pTabs[1] = -sSize.cx * 5;
            pTabs[2] = -sSize.cx * 8;
        }

        // draw the text
        //
        // Note that the SDK dox for GrayString says that you can detect
        // whether GrayString is needed by saying
        //
        //      if (GetSysColor(COLOR_GRAYTEXT) == 0) {
        //          GrayString(...);
        //      } else {
        //          TextOut(...);
        //      }
        //
        // This is incorrect.  The correct test is the one below, which
        // also catches bad color combinations on color devices.
        //
        if (clrText == clrBack) {
            SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
            GrayString(hDC, GetStockObject(GRAY_BRUSH), diflGrayStringProc ,
                       (LPARAM)(LPVOID)&di, 0,
                        lpdis->rcItem.left + DX_TTBITMAP,
                        lpdis->rcItem.top,
                        lpdis->rcItem.right - lpdis->rcItem.left - DX_TTBITMAP,
                        lpdis->rcItem.bottom - lpdis->rcItem.top);
        } else {
            TabbedTextOut(hDC, lpdis->rcItem.left + DX_TTBITMAP, lpdis->rcItem.top, lpsz, lstrlen(lpsz), cTabsList, pTabs, DX_TTBITMAP);
        }

        // and the bitmap if needed
        if (!IsSpecialBpfdi((BPFDI)lpdis->itemData))
        {
            if (((BPFDI)(lpdis->itemData))->bTT) {
                hdcMem = CreateCompatibleDC(hDC);
                if (hdcMem) {
                    hOld = SelectObject(hdcMem, hbmFont);

                    dy = ((lpdis->rcItem.bottom - lpdis->rcItem.top) - DY_TTBITMAP) / 2;

                    BitBlt(hDC, lpdis->rcItem.left, lpdis->rcItem.top + dy,
                        DX_TTBITMAP, DY_TTBITMAP, hdcMem, 0,
                        lpdis->itemState & ODS_SELECTED ? 0 : DY_TTBITMAP, SRCCOPY);

                    if (hOld)
                        SelectObject(hdcMem, hOld);
                    DeleteDC(hdcMem);
                }
            }
        }

        SetTextColor(hDC, rgbText);
        SetBkColor(hDC, rgbBack);

        if (lpdis->itemState & ODS_FOCUS) {
            DrawFocusRect(hDC, &lpdis->rcItem);
        }
    }
}
#undef lpsz
#undef pTabs


/** MeasureItemFontList
 *
 *  Answer the WM_MEASUREITEM message sent from the font list box or
 *  font combo box. shared between the toolbar combo box code and
 *  the font preview property sheet
 *
 *  Entry:
 *      lpmi -> LPMEASUREITEMSTRUCT describing object to be measured
 *
 *  Returns:
 *      TRUE.
 *
 *      lpmi->itemHeight filled with actual item height.
 */

LONG WINAPI MeasureItemFontList(LPMEASUREITEMSTRUCT lpmi)
{
    lpmi->itemHeight = dyItem;
    return TRUE;
}


/** GetItemFontInfo
 *
 *  Returns font information for the current selection in the given
 *  listbox/combobox.
 *
 *  Entry:
 *      hwndList == handle to listbox or combo box to fill
 *                  if NULL, then AUTO is assumed
 *      fListBox == TRUE if hwndList is a listbox, FALSE if a combo box
 *      hProps   == property handle
 *      lpFnt    -> PROPFNT structure (filled in upon return)
 *
 *  Returns:
 *      LB_ERR/CB_ERR on error, index of current selection otherwise
 */

int WINAPI GetItemFontInfo(HWND hwndList, BOOL fListBox, HANDLE hProps, LPPROPFNT lpFnt)
{
    DWORD_PTR dw;
    int index;

    /*
     * Get font defaults;  the nice thing about this call is that
     * it also takes care of calling ChooseBestFont if AUTOSIZE is set,
     * which means we can tell GetFont() to not bother.
     */
    PifMgr_GetProperties(hProps, MAKELP(0,GROUP_FNT),
                  lpFnt, sizeof(PROPFNT), GETPROPS_NONE);

    dw = GetFont(hwndList, fListBox, NULL);
    if (IsSpecialBpfdi((BPFDI)dw))
    {
        return 0;
    }

    index = ((BPFDI)dw)->Index;
    if (index == 0)
        lpFnt->flFnt |= FNT_AUTOSIZE;
    else if (index > 0)
        lpFnt->flFnt &= ~FNT_AUTOSIZE;

    /*
     * Fill the caller's PROPFNT structure with all the font goodies (if any)
     *
     * Note that this does nothing if we ended up picking the AUTO font.
     */
    SetFont(lpFnt, (BPFDI)dw);

    return index;
}


/** MatchCurrentFont
 *
 *  Locates the current font in the indicated list box and
 *  makes him the current selection.
 *
 *  If we are in auto-mode, then "Auto" is selected.
 *
 *  Entry:
 *      hwndList == handle to listbox or combo box
 *      fListBox == TRUE if hwndList is a listbox, FALSE if a combo box
 *      lpFnt    -> PROPFNT structure
 *
 *  Returns:
 *      TRUE if the current font was found and selected.
 */
BOOL WINAPI MatchCurrentFont(HWND hwndList, BOOL fListBox, LPPROPFNT lpFnt)
{
    BPFDI bpfdi;
    DWORD dwCount, dwIndex;
    BOOL  fCurFontIsTt = !!(lpFnt->flFnt & FNT_TT);

    if (lpFnt->flFnt & FNT_AUTOSIZE) {
        /*
         * SORTING-SENSITIVE!  This assumes that "Auto" is at the top
         * of the list.
         */
        lcbSetCurSel(hwndList, fListBox, 0);
        return TRUE;
    }
    dwCount = lcbGetCount(hwndList, fListBox);
    for (dwIndex = 0; dwIndex < dwCount; dwIndex++) {

        bpfdi = lcbGetBpfdi(hwndList, fListBox, dwIndex);

        if (!IsSpecialBpfdi(bpfdi)) {
            // bpfdi = (BPFDI)((DWORD)bpfdi + (DWORD)lpCache);
            if (bpfdi->fdiWidthActual  == lpFnt->cxFontActual &&
                bpfdi->fdiHeightActual == lpFnt->cyFontActual &&
                fCurFontIsTt == (bpfdi->fdiHeightReq != 0)) {

                    lcbSetCurSel(hwndList, fListBox, dwIndex);
                    return TRUE;
            }
        }
    }
    return FALSE;
}

/** AddRasterFontsToFontList
 *
 *  Enumerate the available dimensions for our OEM raster font
 *  and add them to the list or combo box.
 *
 *  Entry:
 *      hwndList        =  List box or combo box to fill with info
 *      fListBox        =  TRUE if hwndList is a listbox, FALSE if a combo box
 *      lpszRasterFaceName
 *
 *  Returns:
 *      TRUE if fonts were enumerated to completion.
 *      FALSE if enumeration failed.  (Out of memory.)
 *
 */
BOOL AddRasterFontsToFontListA(HWND hwndList, BOOL fListBox,
                                       LPCSTR lpszRasterFaceName, INT CodePage)
{
    HDC     hDC;
    BOOL    fSuccess;
    FNTENUMINFO FntEnum;

    hDC = GetDC(hwndList);
    if (!hDC) return FALSE;

    FntEnum.hwndList = hwndList;
    FntEnum.fListBox = fListBox;
    FntEnum.CodePage = CodePage;
    fSuccess = EnumFontFamiliesA(hDC,
                                lpszRasterFaceName,
                                (FONTENUMPROCA)RasterFontEnum,
                                (LPARAM)&FntEnum);
    ReleaseDC(hwndList, hDC);
    return TRUE;
}


/** RasterFontEnum
 *
 *  FONTENUMPROC for enumerating all available dimensions of the OEM
 *  raster font.
 *
 *  This routine is used to load the logical and physical font
 *  dimensions cache with information about raster fonts.
 *
 *  Entry:
 *      lpelf           \
 *      lpntm            > from EnumFonts (see SDK)
 *      nFontType       /
 *      hwndList        =  List box or combo box to fill with info
 *      fListBox        =  TRUE if hwndList is a listbox, FALSE if a combo box
 *
 *  Returns:
 *      TRUE to continue enumeration.
 *      FALSE to stop enumeration.  (Out of memory.)
 */

int CALLBACK RasterFontEnum(ENUMLOGFONTA *lpelf, NEWTEXTMETRICA *lpntm, int nFontType, LPARAM lParam)
{
#define fListBox  (((LPFNTENUMINFO)lParam)->fListBox)
#define hwndList  (((LPFNTENUMINFO)lParam)->hwndList)
#define CodePage (((LPFNTENUMINFO)lParam)->CodePage)
#define lpLogFont (&(lpelf->elfLogFont))

    /*
     * We only care about OEM fixed-pitch fonts.
     */
    if (lpLogFont->lfCharSet != OEMCharsetFromCP(CodePage)
        || (lpLogFont->lfPitchAndFamily & (TMPF_TRUETYPE | TMPF_FIXED_PITCH))
            != TMPF_FIXED_PITCH)
        return TRUE;

    return AddToFontListCache(hwndList,
                              fListBox,
                              0, 0,
                              lpLogFont->lfHeight,
                              lpLogFont->lfWidth,
                              CodePage) != BPFDI_CANCEL;
#undef lpLogFont
#undef fListBox
#undef hwndList
#undef CodePage
}

/** AddToFontListCache
 *
 *  Adds an entry to the font dimension information cache,
 *  growing the cache if necessary.
 *
 *  It also adds the entry to the indicated list box, provided
 *  the entry is not a duplicate.
 *
 *  Returns:
 *      BPFDI of the recently-added font, or BPFDI_CANCEL if out of memory.
 *
 *  Overview:
 *      (1) Grow the cache if necessary.
 *      (2) Add the information to the list/combo box.
 *      (3) Add the information to the cache.
 */
BPFDI AddToFontListCache(HWND hwndList,
                         BOOL fListBox,
                         UINT uHeightReq,
                         UINT uWidthReq,
                         UINT uHeightActual,
                         UINT uWidthActual,
                         UINT uCodePage)
{
    LPVOID  hCache;
    LONG_PTR lCacheSave;
    DWORD   dwIndex, ifdi;
    BPFDI   bpfdi;
    TCHAR   szBuf[MAXDIMENSTRING];
    int     idx;
    
    ASSERT(!((uHeightReq==0) && (uWidthReq==0) && (uHeightActual==0) && (uWidthActual==0)));
    /* Reject too-large fonts out of hand. */
    if (uHeightActual > MAX_FONT_HEIGHT) {
        return BPFDI_IGNORE;
    }

    /*
     * FIRST, determine whether this font entry has already been cached
     */

    // we maintain two set of cache entries in case we have two code page
    // to support
    // 
    idx = IsBilingualCP(uCodePage) ? 1 : 0; 
    
    for (ifdi = 0, bpfdi = (BPFDI)((DWORD_PTR)lpCache + bpfdiStart[idx]); ifdi < cfdiCache[idx]; ++ifdi, ++bpfdi)
    {
        if (bpfdi->fdiWidthReq == uWidthReq &&
            bpfdi->fdiHeightReq == uHeightReq &&
            bpfdi->fdiWidthActual == uWidthActual &&
            bpfdi->fdiHeightActual == uHeightActual)
                goto UpdateListCombo;
    }

    /*
     * Grow the cache if necessary.
     */
    if (cfdiCache[idx] >= cfdiCacheActual[idx]) {

        /*
         * save offset from beginning of cache
         */
        bpfdi = (BPFDI)((DWORD_PTR)bpfdi - (DWORD_PTR)lpCache);

        /*
         * save current lpCache value so can adjust entries in listbox
         * when we're done...
         */
        lCacheSave = (LONG_PTR)lpCache;
        hCache = LocalReAlloc(lpCache,
        (cfdiCacheActual[0] + cfdiCacheActual[1] + FDI_TABLE_INC) *
        sizeof(FONTDIMENINFO), LMEM_ZEROINIT|LMEM_MOVEABLE);
        if (!hCache)
            return BPFDI_CANCEL;
        lpCache = hCache;
        
        if (!idx && IsBilingualCP(g_uCodePage)) {
            /*
             * We need to shift 2nd cache before using expanded 1st chache
             */
            BPFDI bpfdi2;
            for (ifdi = cfdiCache[1],
                              bpfdi2 = (BPFDI)((DWORD_PTR)lpCache + bpfdiStart[1]) + ifdi - 1 + FDI_TABLE_INC ;
                                                  ifdi ; ifdi--, bpfdi2--) {
                *bpfdi2 = *(bpfdi2 - FDI_TABLE_INC);
            }
            bpfdiStart[1] += FDI_TABLE_INC;
        }
        /* restore bpfdi from saved offset */
        bpfdi = (BPFDI)((DWORD_PTR)lpCache + (DWORD_PTR)bpfdi);
        cfdiCacheActual[idx] += FDI_TABLE_INC;

        /*
         * Convert lCacheSave to an offset...
         */
        lCacheSave = (LONG_PTR)lpCache - lCacheSave;

        if (lCacheSave)
        {
            /*
             * Now, adjust each entry in the listbox to account for the new
             * relocated cache position..
             */

            dwIndex = lcbGetCount(hwndList, fListBox);
            for(ifdi = 0; ifdi < dwIndex; ifdi++)
            {
                LONG_PTR lBpfdi;

                lBpfdi = (LONG_PTR)lcbGetItemDataPair(hwndList, fListBox, ifdi);
                if (!IsSpecialBpfdi((BPFDI)lBpfdi))
                {
                    lBpfdi += lCacheSave;
                    lcbSetItemDataPair(hwndList, fListBox, ifdi, lBpfdi, ((BPFDI)lBpfdi)->bTT);
                }
            }
        }
    }

    /*
     * Now add the information to the cache.  All the casting on bpfdiCache
     * is just to inhibit a bogus compiler complaint.
     */
    bpfdi->fdiWidthReq  = uWidthReq;
    bpfdi->fdiHeightReq = uHeightReq;

    bpfdi->fdiWidthActual  = uWidthActual;
    bpfdi->fdiHeightActual = uHeightActual;

    cfdiCache[idx]++;

  UpdateListCombo:

    if (hwndList) {
        /*
         * Add the string to the list/combo box if it isn't there already.
         */
        wsprintf(szBuf, TEXT("\t%2d\tx\t%2d"), uWidthActual, uHeightActual);

        dwIndex = lcbFindStringExact(hwndList, fListBox, szBuf);

        if (IsDlgError(dwIndex)) {
            /*
             * Not already on the list.  Add it.
             */
            dwIndex = lcbAddString(hwndList, fListBox, szBuf);

            if (IsDlgError(dwIndex)) {
                return BPFDI_CANCEL;
            }
            lcbSetItemDataPair(hwndList, fListBox, dwIndex,
                               bpfdi, uHeightReq);
        }
    }
    return bpfdi;
}


/** AddTrueTypeFontsToFontListA
 *
 *  To avoid rasterizing all the fonts unnecessarily, we load the
 *  information from the szTTCacheSection font cache.
 *
 *  Note that the cache information is not validated!  We just
 *  assume that if the value is in the cache, it is valid.
 *
 *  Entry:
 *      hwndList        =  List box or combo box to fill with info
 *      fListBox        =  TRUE if hwndList is a listbox, FALSE if a combo box
 *      lpszTTFaceName
 *
 *  Returns:
 *      TRUE if fonts were enumerated to completion.
 *      FALSE if enumeration failed.  (Out of memory.)
 *
 *  Caveats:
 *      The ParseIniWords call assumes that the values were written
 *      by AddOneNewTrueTypeFontToFontList, who wrote them out so
 *      that a single call to ParseIniWords will read the height and
 *      width directly into a dwHeightWidth.
 *
 *      Similarly, the second ParseIniWords reads the item directly into
 *      a dwHeightWidth.
 */

BOOL AddTrueTypeFontsToFontListA(HWND hwndList, BOOL fListBox,
                                        LPSTR lpszTTFaceName, INT CodePage)
{
    LPTSTR  pszBuf;
    LPTSTR  pszBufNew;
    LPTSTR  psz;
    LPTSTR  lpszNext;
    DWORD   dwHWReq;
    DWORD   dwHWActual;
    BOOL    fSuccess;
    DWORD   cchBuf;
    DWORD   cchActual;
    int     i;
    int     idx = IsBilingualCP(CodePage) ? 1 : 0;
    
    /*
     * See if we can load everything out of the szTTCacheSection.
     *
     * There is no API to get the size of a profile string, so we
     * have to fake it by reading, reallocing, and reading again
     * until it all fits.
     *
     * The initial value of 1024 characters means that we can handle
     * up to 128 font sizes.  A comfortable number, we hope.
     */

    cchBuf = 1024;
    cchActual = 0;
    pszBufNew = (LPTSTR)LocalAlloc(LPTR, cchBuf*sizeof(TCHAR));

    while (pszBufNew) {
        pszBuf = pszBufNew;
        cchActual = GetPrivateProfileString(szTTCacheSection[idx], NULL,
                                         c_szNULL, pszBuf, cchBuf, szSystemINI);
        if (cchActual < cchBuf - 5) goto Okay;

        cchBuf += 512;
        pszBufNew = (LPTSTR)LocalReAlloc(pszBuf, cchBuf*sizeof(TCHAR), LMEM_MOVEABLE|LMEM_ZEROINIT);
    }

    /* Bleargh.  Too much stuff in the cache.  Punt it and start anew. */
    goto FreshStart;

Okay:

    fSuccess = FALSE;

    /*
     *  In the time between flushing the cache and reloading it here,
     *  a handful of fonts may have gotten added to the cache due to
     *  WinOldAp trying to realize the font it got back.  So consider the
     *  font cache decent if there are at least ten fonts in it.
     */
    if (cchActual >= 4 * 10) {

        /*
         * We found cache information.  Party away.
         */

        psz = pszBuf;
        while (*psz) {

            if (ParseIniWords(psz, (PWORD)&dwHWReq, 2, &lpszNext) != 2 ||
                GetIniWords(szTTCacheSection[idx], psz,
                            (PWORD)&dwHWActual, 2, szSystemINI) != 2) {
                /* Font cache looks bogus.  Start with a new one. */
                goto FreshStart;
            }

            if (AddToFontListCache(hwndList, fListBox,
                                   (UINT)HIWORD(dwHWReq),
                                   (UINT)LOWORD(dwHWReq),
                                   (UINT)HIWORD(dwHWActual),
                                   (UINT)LOWORD(dwHWActual),
                                   CodePage) == BPFDI_CANCEL)
                goto E0;
                
            psz = (LPTSTR)(lpszNext + 1);       /* Skip past the NUL */
        }

    }
    else
    {
FreshStart:
        /* Blast the old cache, just make sure we have a clean slate */
        WritePrivateProfileString(szTTCacheSection[idx], NULL, NULL, szSystemINI);

        /* No cache available.  Need to build one. */
        for (i = 0; i < NUMINITIALTTHEIGHTS; i++) 
        {
            if (rgwInitialTtHeights[i]) 
            {
                AddOneNewTrueTypeFontToFontListA(hwndList, fListBox,
                                                0, (UINT)rgwInitialTtHeights[i],
                                                lpszTTFaceName, CodePage);
            }
        }
    }

    fSuccess = TRUE;
E0:
    EVAL(LocalFree(pszBuf) == NULL);
    return fSuccess;
}


/*  Given height and width, synthesize a TrueType font with those
 *  dimensions and record the actual font height and width in
 *  the persistent font cache, as well as a FDI.
 *
 *  Entry:
 *      hwndList        =  List box or combo box to fill with info
 *      fListBox        =  TRUE if hwndList is a listbox, FALSE if a combo box
 *      wHeight         =  Desired font height
 *      wWidth          =  Desired font width (can be zero for "default")
 *      lpszTTFaceName
 *
 *  Returns:
 *      BPFDI of font dimension info, or BPFDI_CANCEL on failure.
 *
 *  Caveats:
 *      The wsprintf assumes that the fdiWidthReq and
 *      fdiHeightReq fields appear in the indicated order,
 *      because the values will be read into a dwHeightWidth later.
 *
 *      Similarly for the WriteIniWords.
 */

BPFDI AddOneNewTrueTypeFontToFontListA(HWND hwndList,
                                      BOOL fListBox,
                                      UINT uWidth, UINT uHeight,
                                      LPSTR lpszTTFaceName, INT CodePage)
{
    BPFDI   bpfdi;
    HDC     hDC;
    HFONT   hFont;
    SIZE    sSize;
    HFONT   hFontPrev;
    DWORD   dwHeightWidth;
    TCHAR   szBuf[MAXDIMENSTRING];

    int     idx;
    BYTE    bCharset;
    DWORD   fdwClipPrecision;

    bpfdi = BPFDI_CANCEL;

    hDC = GetDC(NULL);          /* Get a screen DC */
    if (!hDC) goto E0;
    
    // choose charset, clip precision based on codepage
    // 0xFE is a hack for japanese platform
    //
    bCharset = (CodePage == CP_JPN? 0xFE: OEMCharsetFromCP(CodePage));
    
    if (CodePage == CP_US)
        fdwClipPrecision = CLIP_DEFAULT_PRECIS|(g_uCodePage == CP_WANSUNG? CLIP_DFA_OVERRIDE: 0);
    else
        fdwClipPrecision = CLIP_DEFAULT_PRECIS;

    hFont = CreateFontA((INT)uHeight, (INT)uWidth, 0, 0, 0, 0, 0, 0,
               bCharset, OUT_TT_PRECIS,
               fdwClipPrecision, 0, FIXED_PITCH | FF_DONTCARE, lpszTTFaceName);
               
    if (!hFont) goto E1;

    hFontPrev = SelectObject(hDC, hFont);
    if (!hFontPrev) goto E2;

    if (GetTextExtentPoint32(hDC, szZero, 1, &sSize))
    {
        dwHeightWidth = (sSize.cy << 16) | (sSize.cx & 0x00FF);
    }
    else
    {
        dwHeightWidth = 0;
    }

    if (!dwHeightWidth) goto E3;

    if (IsBilingualCP(CodePage) && (HIWORD(dwHeightWidth)%2))
        goto E3;

    wsprintf(szBuf, TEXT("%d %d"), uWidth, uHeight);

    idx = IsBilingualCP(CodePage) ? 0 : 1;
    
    WriteIniWords(szTTCacheSection[idx], szBuf, (PWORD)&dwHeightWidth, 2, szSystemINI);

    bpfdi = AddToFontListCache(hwndList, fListBox, uHeight, uWidth,
                               (UINT)sSize.cy, (UINT)sSize.cx,
                               CodePage);

E3: SelectObject(hDC, hFontPrev);
E2: DeleteObject(hFont);
E1: ReleaseDC(0, hDC);
E0: return bpfdi;

}


/*  Returns the BPFDI corresponding to the currently selected font in
 *  the indicated list or combo box, or BPFDI_CANCEL on error.
 *
 *  Entry:
 *      hwndList == handle to listbox or combo box to fill
 *                  if NULL, then AUTO font calculation is assumed
 *      fListBox == TRUE if hwndList is a listbox, FALSE if a combo box
 *      pfi      -> FNTINFO structure
 *                  if pfi is NULL, then AUTO font calculation is ignored
 *  Returns:
 *      BPFDI of the current selection, or BPFDI_CANCEL on error.
 */
DWORD_PTR GetFont(HWND hwndList, BOOL fListBox, PFNTINFO pfi)
{
    DWORD dwIndex = 0;
    BPFDI bpfdi = BPFDI_CANCEL;

    if (!hwndList) {            // just do AUTO calculations
        if (!pfi)
            goto Exit;          // whoops, can't even do those
        goto ChooseBest;
    }
    dwIndex = lcbGetCurSel(hwndList, fListBox);
    if (!IsDlgError(dwIndex)) {

        if (pfi)
            pfi->fntProposed.flFnt &= ~FNT_AUTOSIZE;

        bpfdi = lcbGetBpfdi(hwndList, fListBox, dwIndex);

        if (bpfdi == BPFDI_AUTO && pfi) {
            pfi->fntProposed.flFnt |= FNT_AUTOSIZE;

ChooseBest:
            bpfdi = ChooseBestFont((UINT)pfi->winOriginal.cxCells,
                                   (UINT)pfi->winOriginal.cyCells,
                                   (UINT)pfi->winOriginal.cxClient,
                                   (UINT)pfi->winOriginal.cyClient,
                                   (UINT)pfi->fntProposed.flFnt,
                                    (INT)pfi->fntProposed.wCurrentCP);
        }
        // Set the index of the current selection (HIWORD
        // of the return code) to LB_ERR if there's an error

        if (bpfdi == BPFDI_CANCEL)
            dwIndex = (DWORD)LB_ERR;
    }
  Exit:
    if (!IsSpecialBpfdi(bpfdi))
    {
        bpfdi->Index = dwIndex;
    }

    return (DWORD_PTR)bpfdi;
}


/*  Copies data from the given BPFDI to the given PROPFNT structure.
 *
 *  Entry:
 *      lpFnt = pointer to PROPFNT structure
 *      bpfdi = based pointer to a FONTDIMENINFO structure;
 *              if a special BPFDI_* constant, no font info is changed
 *  Returns:
 *      Nothing
 */
void SetFont(LPPROPFNT lpFnt, BPFDI bpfdi)
{
    if (!IsSpecialBpfdi(bpfdi)) 
    {
        lpFnt->flFnt &= ~(FNT_RASTER | FNT_TT);

        if (bpfdi->fdiHeightReq == 0) 
        {
            /* Raster font */
            lpFnt->flFnt |= FNT_RASTER;
            lpFnt->cxFont = lpFnt->cxFontActual = (WORD) bpfdi->fdiWidthActual;
            lpFnt->cyFont = lpFnt->cyFontActual = (WORD) bpfdi->fdiHeightActual;
        }
        else 
        {
            /* TrueType font */
            lpFnt->flFnt |= FNT_TT;
            lpFnt->cxFont = (WORD) bpfdi->fdiWidthReq;
            lpFnt->cyFont = (WORD) bpfdi->fdiHeightReq;
            lpFnt->cxFontActual = (WORD) bpfdi->fdiWidthActual;
            lpFnt->cyFontActual = (WORD) bpfdi->fdiHeightActual;
        }
    }
}


/*  Performs the following calculation in LONG arithmetic to avoid
 *  overflow:
 *      return = n1 * m / n2
 *  This can be used to make an aspect ration calculation where n1/n2
 *  is the aspect ratio and m is a known value.  The return value will
 *  be the value that corresponds to m with the correct apsect ratio.
 */

//
// <This is defined as a macro for Win32 >
//

/*  Scales a point to be preview-sized instead of screen-sized.
 *  Depends on the global vars cxScreen and cyScreen established at init.
 */

void AspectPoint(LPRECT lprcPreview, LPPOINT lppt)
{
    lppt->x = AspectScale(lprcPreview->right, cxScreen, lppt->x);
    lppt->y = AspectScale(lprcPreview->bottom, cyScreen, lppt->y);
}

/*  AspectRect
 *
 *  Scales a rectangle to be preview-sized instead of screen-sized.
 *  Depends on the global vars cxScreen and cyScreen established at init.
 */

void AspectRect(LPRECT lprcPreview, LPRECT lprc)
{
    AspectPoint(lprcPreview, &((LPPOINT)lprc)[0]); /* Upper left corner */
    AspectPoint(lprcPreview, &((LPPOINT)lprc)[1]); /* Lower right corner */
}

/*  Given a BPFDI, create a font that corresponds to it.
 *
 *  Entry:
 *      bpfdi       -> FDI describing the font we want to create
 *      pfi         -> proposed font info structure
 *
 *  Returns:
 *      HFONT that was created.
 */
HFONT CreateFontFromBpfdi(BPFDI bpfdi, PFNTINFO pfi)
{
    HFONT hf;
    int   fdwClipPrecision;
    BYTE  bT2Charset;

    // a hack for japanese charset
    bT2Charset = (pfi->fntProposed.wCurrentCP == CP_JPN? 
                  0xFE: OEMCharsetFromCP(pfi->fntProposed.wCurrentCP));
    
    if (pfi->fntProposed.wCurrentCP == CP_US)
        fdwClipPrecision = CLIP_DEFAULT_PRECIS|(g_uCodePage == CP_WANSUNG? CLIP_DFA_OVERRIDE: 0);
    else
        fdwClipPrecision = CLIP_DEFAULT_PRECIS;
        
    if (bpfdi->fdiHeightReq == 0) {
        /* Raster font */
        hf = CreateFontA(bpfdi->fdiHeightActual, bpfdi->fdiWidthActual,
            0, 0, 0, 0, 0, 0, (BYTE)OEMCharsetFromCP(pfi->fntProposed.wCurrentCP), 
            OUT_RASTER_PRECIS, fdwClipPrecision,
            0, FIXED_PITCH | FF_DONTCARE, pfi->fntProposed.achRasterFaceName);
    } else {
        /* a TrueType font */
        hf = CreateFontA(bpfdi->fdiHeightReq, bpfdi->fdiWidthReq,
            0, 0, 0, 0, 0, 0, (BYTE)bT2Charset, OUT_TT_PRECIS, fdwClipPrecision,
            0, FIXED_PITCH | FF_DONTCARE, pfi->fntProposed.achTTFaceName);
    }

    return hf;
}


/** FontSelInit
 *
 *  Obtain the various font selection penalties from SYSTEM.INI
 *  and force the values into range.
 *
 *  Entry:
 *      rgwInitialTtHeights contains default values for sizes.
 *
 *  Exit:
 *      rgwInitialTtHeights contains actual values for sizes.
 */

void FontSelInit(void)
{
    GetIniWords(szNonWinSection, szTTInitialSizes,
                rgwInitialTtHeights, sizeof(rgwInitialTtHeights)/sizeof(WORD), szSystemINI);
}


/*  Convert logical dimensions for a TrueType font into physical
 *  dimensions.  If possible, we get this information from the
 *  font dimension cache, but in the case where this is not possible,
 *  we synthesize the font and measure him directly.
 *
 *  Entry:
 *      dxWidth  = logical font width
 *      dyHeight = logical font height
 *
 *  Returns:
 *      BPFDI pointing to dimension information, or BPFDI_CANCEL on failure.
 */

BPFDI GetTrueTypeFontTrueDimensions(UINT dxWidth, UINT dyHeight, INT CodePage)
{
    IFDI    ifdi;
    BPFDI   bpfdi;
    int     idx = IsBilingualCP(CodePage)? 1 : 0;
    for (ifdi = 0, bpfdi = (BPFDI)((DWORD_PTR)lpCache + bpfdiStart[idx]);  
                    ifdi < cfdiCache[idx];  ifdi++, bpfdi++)
    {
        if (bpfdi->fdiWidthReq  == dxWidth &&
            bpfdi->fdiHeightReq == dyHeight) {
            return bpfdi;
        }
    }

    /*
     * The font dimensions have not been cached.  We have to create it.
     */
    return (BPFDI)AddOneNewTrueTypeFontToFontListA(0, 0, dxWidth, dyHeight,
                                                 szTTFaceName[idx], CodePage);
}


/*  Look for a font that matches the indicated dimensions, creating
 *  one if necessary.
 *
 *  But we never create a font which is too narrow or too short.
 *  The limits are controlled by the ptNonAspectMin variable.
 *
 *  Entry:
 *      dxWidth  = desired font width
 *      dyHeight = desired font height
 *      fPerfect = see below
 *
 *          If fPerfect is TRUE, then a perfect match is requested
 *          from the font cache (we should not try to synthesize a font).
 *          In which case, the sign of dyHeight determines whether a
 *          raster font (positive) or TrueType font (negative) is
 *          desired.  If a perfect match cannot be found, then we
 *          return BPFDI_CANCEL.
 *
 *  Returns:
 *      BPFDI of of the font that matches the best.
 *      BPFDI_CANCEL if no font could be found.
 */
BPFDI FindFontMatch(UINT dxWidth, UINT dyHeight, LPINT lpfl, INT CodePage)
{
    IFDI    ifdi;
    BPFDI   bpfdi;
    BPFDI   bpfdiBest = BPFDI_CANCEL;
    PENALTY pnlBest = SENTINELPENALTY;
    int     idx;

    int fl = *lpfl;
    /*
     * First, see if a perfect match already exists.
     */
    idx = IsBilingualCP(CodePage) ? 1 : 0;
    for (ifdi = 0, bpfdi = (BPFDI)((DWORD_PTR)lpCache+bpfdiStart[idx]);  
                    ifdi < cfdiCache[idx];  ifdi++, bpfdi++)
    {

        if (fl & FFM_RESTRICTED) {
            /* Deal with the restrictions.
             * Reject the font if it is raster but we want TTONLY, or v.v.
             *
             * The condition below reads as
             *
             *      If (is a raster font != want a raster font)
             */
            if (!bpfdi->fdiHeightReq != (fl == FFM_RASTERFONTS)) {
                continue;
            }
        }
        if (bpfdi->fdiHeightActual == dyHeight && bpfdi->fdiWidthActual == dxWidth) {
            *lpfl = FFM_PERFECT;
            return bpfdi;
    }   }

    if (fl != FFM_TTFONTS)
        return BPFDI_CANCEL;
    /*
     * We got here if we couldn't find a perfect match.
     *
     * Adjust the requested height and width for aspect ratio
     * constraints.  If adjustments are necessary, trust the height.
     *
     * Comparisons are as WORDs (unsigned) so that a setting of "-1 -1"
     * lets the user forbid all non-aspect ratio fonts.
     */
    if (dyHeight < (UINT)ptNonAspectMin.y || dxWidth < (UINT)ptNonAspectMin.x) {
        dxWidth = 0;
    }
    return GetTrueTypeFontTrueDimensions(dxWidth, dyHeight, CodePage);
}

/*  We have decided whether the desired size is larger or smaller.
 *  Compute the penalty corresponding to the Initial and Scale.
 *
 *  Entry:
 *      ppnlp   -> PENALTYPAIR to apply
 *      dSmaller = the smaller dimension
 *      dLarger  = the larger dimension
 *
 *  Exit:
 *      Returns penalty to apply to the difference in dimensions.
 */
PENALTY ComputePenaltyFromPair(PPENALTYPAIR ppnlp,
                               UINT dSmaller, UINT dLarger)
{
    return (ppnlp->pnlInitial +
            ppnlp->pnlScale - MulDiv(ppnlp->pnlScale, dSmaller, dLarger));
}


/*  Compute the penalty depending on whether the desired size
 *  is smaller, equal to, or larger than the actual size.
 *
 *  Entry:
 *      ppnll   -> PENALTYLIST to apply
 *      dActual  = the actual dimension
 *      dDesired = the desired dimension
 *
 *  Exit:
 *      Returns penalty to apply to the difference in dimensions,
 *      choosing between the Overshoot and Shortfall PENALTYPAIRS,
 *      accordingly.
 */
PENALTY ComputePenaltyFromList(PPENALTYLIST ppnll,
                               UINT dActual, UINT dDesired)
{
    if (dActual == dDesired)
        return 0;

    if (dActual < dDesired)
        return ComputePenaltyFromPair(&ppnll->pnlpOvershoot, dActual, dDesired);

    return ComputePenaltyFromPair(&ppnll->pnlpShortfall, dDesired, dActual);
}


/** ComputePenalty
 *
 *  Compute the total penalty associated to a window size.
 *
 *  Entry:
 *      dxCells  = width of window in cells
 *      dyCells  = height of window in cells
 *      dxClient = actual horizontal size of window
 *      dyClient = actual vertical   size of window
 *      dxFont   = width of one character in the font
 *      dyFont   = height of one character in the font
 *
 *  Exit:
 *      Returns total penalty associated to a window of the indicated
 *      size with a font of the indicated dimensions.
 */
PENALTY ComputePenalty(UINT cxCells,  UINT cyCells,
                       UINT dxClient, UINT dyClient,
                       UINT dxFont,   UINT dyFont)
{
    return
        (ComputePenaltyFromList(&pnllX, dxClient, dxFont * cxCells) +
         ComputePenaltyFromList(&pnllY, dyClient, dyFont * cyCells));
}


/** ChooseBestFont
 *
 *  Determine which font looks best for the specified window size
 *  by picking the one which has the smallest penalty.
 *
 *  Entry:
 *      dxCells = width of window in cells
 *      dyCells = height of window in cells
 *      dxClient= width of window we want to fit into
 *      dyClient= height of window we want to fit into
 *      fl      = font pool flags
 *
 *  Returns:
 *      Word offset from lpFontTable of the font we've decided to use.
 *      BPFDI_CANCEL if no font could be found.  (Should never happen.)
 *
 *  NOTE!
 *      We do *not* FontEnum through all the fonts because that would be
 *      too slow.  Instead, we inspect the cache of available font
 *      dimensions, and only after we've chosen the best font do we
 *      load all his other info.
 *
 *      This means that if the user installs new fonts, we won't see
 *      them until the cache is updated on receipt of a WM_FONTCHANGEff
 *      message, or the user either (1) pulls down the font list box,
 *      or (2) calls up the font selection dialog box.
 */

BPFDI ChooseBestFont(UINT cxCells, UINT cyCells, UINT dxClient, UINT dyClient,
                                                         INT fl, INT CodePage)
{
    int     flTemp;
    DWORD    ifdi;
    BPFDI   bpfdi;
    PENALTY pnl;
    UINT    dxWidth, dyHeight;
    BPFDI   bpfdiBest = BPFDI_CANCEL;
    PENALTY pnlBest = SENTINELPENALTY;
    int     idx;
    static int prev_CodePage;  // Only Japan is interested in prev_CodePage.

    /*
     * First, synthesize the theoretical best match.
     */
    if (!cxCells)
        cxCells = 80;           // if we get called with no real data,
    if (!cyCells)               // at least try to do something reasonable
        cyCells = 25;

    //
    // In the case where the values passed in don't make sense,
    // we default to raster 8x12.
    //
    dxWidth = (dxClient >= cxCells)? dxClient / cxCells : 8;
    dyHeight = (dyClient >= cyCells)? dyClient / cyCells : 12;

    //
    // Now, if we bad values, make some sense out of bad values for
    // dxClient & dyClient
    //

    if ((dxClient==0) || (dyClient==0))
    {
        dxClient = dxWidth * 80;
        dyClient = dyHeight * 25;
    }

    flTemp = 0;
    if ((fl & FNT_BOTHFONTS) != FNT_BOTHFONTS) {
        flTemp = FFM_RASTERFONTS;
        if (fl & FNT_TTFONTS)
            flTemp = FFM_TTFONTS;
    }
    bpfdi = FindFontMatch(dxWidth, dyHeight, &flTemp, CodePage);
    if (flTemp == FFM_PERFECT)
    {
        prev_CodePage = CodePage;
        return bpfdi;
    }

    idx = IsBilingualCP(CodePage)? 1 : 0;
    for (ifdi = 0, bpfdi = (BPFDI)((DWORD_PTR)lpCache+bpfdiStart[idx]);  
                    ifdi < cfdiCache[idx];  ifdi++, bpfdi++)
    {
        // If the font pool is restricted, then only look at like fonts

        if (flTemp)
            if (!bpfdi->fdiHeightReq != (flTemp == FFM_RASTERFONTS))
                continue;

// was ifdef JAPAN (hack)
// to prevent DOS_BOX shrinking which occurs toggling CP437 & CP932,
// just select one size bigger font when change CP437 to CP932
        if (CodePage == 932 && prev_CodePage == 437) {
           if (dxWidth < bpfdi->fdiWidthActual) {
              if (bpfdiBest->fdiWidthActual > bpfdi->fdiWidthActual)
                 bpfdiBest = bpfdi;
              else if (bpfdiBest->fdiWidthActual == bpfdi->fdiWidthActual &&
                       bpfdiBest->fdiHeightActual > bpfdi->fdiHeightActual)
                 bpfdiBest = bpfdi;
           }
           else {
              if (dxWidth == bpfdi->fdiWidthActual) {
                 if (bpfdi->fdiHeightActual > dyHeight &&
                     bpfdiBest->fdiHeightActual > bpfdi->fdiHeightActual)
                    bpfdiBest = bpfdi;
              }
           }
        }
        else 
// was the end of ifdef JAPAN
        {
        pnl = 0;
        if (bpfdi->fdiHeightReq)
            pnl = pnlTrueType;

        pnl += ComputePenalty(cxCells, cyCells,
                              dxClient, dyClient,
                              bpfdi->fdiWidthActual,
                              bpfdi->fdiHeightActual);

        if (pnl <= pnlBest) {
            pnlBest = pnl;
            bpfdiBest = bpfdi;
        }
        }
    }
// was ifdef JAPAN
    prev_CodePage = CodePage;
// was end of ifdef JAPAN
    return bpfdiBest;
}