From 34c4bdab9a695d76cc393c87e23a5cf12b3e20ae Mon Sep 17 00:00:00 2001 From: ehouse Date: Tue, 9 Sep 2008 12:31:02 +0000 Subject: [PATCH] In case where board can't fully fill screen, track the rects on either side and erase them when they're invalidated. When seeking best-fit font, pull glyphs to measure from dictionary rather than assuming A-Z. Speed font measuring code by passing over all glyphs only once, noting tallest and lowest-extending then measuring only those two as smaller sizes are tried. This *may* make the process fast enough that I don't need to cache the information across boots: need to try on real hardware. --- xwords4/wince/cedraw.c | 354 ++++++++++++++++++++++++++++++++--------- xwords4/wince/cemain.c | 53 +++++- xwords4/wince/cemain.h | 29 +--- 3 files changed, 337 insertions(+), 99 deletions(-) diff --git a/xwords4/wince/cedraw.c b/xwords4/wince/cedraw.c index 4f1106137..015ec47c1 100755 --- a/xwords4/wince/cedraw.c +++ b/xwords4/wince/cedraw.c @@ -38,6 +38,8 @@ #define DRAW_FUNC_NAME(nam) ce_draw_ ## nam #endif +//#define MEASURE_OLD_WAY + #define CE_MINI_V_PADDING 6 #define CE_MINIW_PADDING 0 #define CE_SCORE_PADDING -3 @@ -53,6 +55,28 @@ # define TREAT_AS_CURSOR(d,f) (((f) & CELL_ISCURSOR) != 0) #endif + +typedef enum { + RFONTS_TRAY + ,RFONTS_TRAYVAL + ,RFONTS_CELL + ,RFONTS_PTS + + ,N_RESIZE_FONTS +} RFIndex; + +typedef struct _PenColorPair { + COLORREF ref; + HGDIOBJ pen; +} PenColorPair; + +typedef struct _FontCacheEntry { + HFONT setFont; + XP_U16 setFontHt; + XP_U16 offset; + XP_U16 actualHt; +} FontCacheEntry; + struct CEDrawCtx { DrawCtxVTable* vtable; @@ -86,6 +110,7 @@ struct CEDrawCtx { static void ceClearToBkground( CEDrawCtx* dctx, const XP_Rect* rect ); static void ceDrawBitmapInRect( HDC hdc, const RECT* r, HBITMAP bitmap ); static void ceClipToRect( HDC hdc, const RECT* rt ); +static void ceClearFontCache( CEDrawCtx* dctx ); static void XPRtoRECT( RECT* rt, const XP_Rect* xprect ) @@ -243,6 +268,147 @@ ERROR #endif } /* ceClipToRect */ +static void +makeTestBuf( CEDrawCtx* dctx, XP_UCHAR* buf, XP_UCHAR bufLen, RFIndex index ) +{ + switch( index ) { + case RFONTS_TRAY: + case RFONTS_CELL: { + Tile tile; + Tile blank = (Tile)-1; + const DictionaryCtxt* dict = dctx->dict; + XP_U16 nFaces = dict_numTileFaces( dict ); + Tile tiles[nFaces]; + XP_U16 nOut = 0; + XP_ASSERT( !!dict && nFaces < bufLen ); + if ( dict_hasBlankTile(dict) ) { + blank = dict_getBlankTile( dict ); + } + for ( tile = 0; tile < nFaces; ++tile ) { + if ( tile != blank ) { + tiles[nOut++] = tile; + } + } + (void)dict_tilesToString( dict, tiles, nOut, buf, bufLen ); + } + break; + case RFONTS_TRAYVAL: + strcpy( buf, "0" ); /* all numbers the same :-) */ + break; + case RFONTS_PTS: + strcpy( buf, "Pts:0?" ); + break; + case N_RESIZE_FONTS: + XP_ASSERT(0); + } + XP_LOGF( "%s=>%s", __func__, buf ); +} /* makeTestBuf */ + +#ifndef MEASURE_OLD_WAY +static void +ceMeasureGlyph( HDC hdc, HBRUSH white, wchar_t glyph, + XP_U16 minTopSeen, XP_U16 maxBottomSeen, + XP_U16* top, XP_U16* bottom ) +{ + SIZE size; + XP_U16 xx, yy; + XP_Bool done; + + GetTextExtentPoint32( hdc, &glyph, 1, &size ); + RECT rect = { 0, 0, size.cx, size.cy }; + FillRect( hdc, &rect, white ); + DrawText( hdc, &glyph, 1, &rect, DT_TOP | DT_LEFT ); + +/* char tbuf[size.cx+1]; */ +/* for ( yy = 0; yy < size.cy; ++yy ) { */ +/* XP_MEMSET( tbuf, 0, size.cx+1 ); */ +/* for ( xx = 0; xx < size.cx; ++xx ) { */ +/* COLORREF ref = GetPixel( hdc, xx, yy ); */ +/* XP_ASSERT( ref != CLR_INVALID ); */ +/* strcat( tbuf, ref==0? " " : "x" ); */ +/* } */ +/* XP_LOGF( "line[%.2d] = %s", yy, tbuf ); */ +/* } */ + + /* Find out if this guy's taller than what we have */ + for ( done = XP_FALSE, yy = 0; yy < minTopSeen && !done; ++yy ) { + for ( xx = 0; xx < size.cx; ++xx ) { + COLORREF ref = GetPixel( hdc, xx, yy ); + if ( ref == CLR_INVALID ) { + break; /* done this line */ + } else if ( ref == 0 ) { /* a pixel set! */ + *top = yy; + done = XP_TRUE; + break; + } + } + } + + /* Extends lower than seen */ + for ( done = XP_FALSE, yy = size.cy - 1; yy > maxBottomSeen && !done; --yy ) { + for ( xx = 0; xx < size.cx; ++xx ) { + COLORREF ref = GetPixel( hdc, xx, yy ); + if ( ref == CLR_INVALID ) { + break; + } else if ( ref == 0 ) { /* a pixel set! */ + *bottom = yy; + done = XP_TRUE; + break; + } + } + } +/* XP_LOGF( "%s: top: %d; bottom: %d", __func__, *top, *bottom ); */ +} /* ceMeasureGlyph */ + +static void +ceMeasureGlyphs( CEDrawCtx* dctx, HDC hdc, /* HFONT font, */wchar_t* str, + XP_U16* hasMinTop, XP_U16* hasMaxBottom ) +{ + HBRUSH white = dctx->brushes[CE_WHITE_COLOR]; + XP_U16 ii; + XP_U16 len = wcslen(str); + XP_U16 minTopSeen, maxBottomSeen; + XP_U16 maxBottomIndex = 0; + XP_U16 minTopIndex = 0; + + minTopSeen = 1000; /* really large... */ + maxBottomSeen = 0; + for ( ii = 0; ii < len; ++ii ) { + XP_U16 thisTop, thisBottom; + + ceMeasureGlyph( hdc, white, str[ii], + minTopSeen, maxBottomSeen, + &thisTop, &thisBottom ); + if ( thisBottom > maxBottomSeen ) { + maxBottomSeen = thisBottom; + maxBottomIndex = ii; + } + if ( thisTop < minTopSeen ) { + minTopSeen = thisTop; + minTopIndex = ii; + } + } + +/* XP_LOGF( "offset: %d; height: %d", minTopSeen, maxBottomSeen - minTopSeen + 1 ); */ +/* XP_LOGF( "offset came from %d; height from %d", minTopIndex, maxBottomIndex ); */ + *hasMinTop = minTopIndex; + *hasMaxBottom = maxBottomIndex; +} /* ceMeasureGlyphs */ +#endif + +static void +ceClearFontCache( CEDrawCtx* dctx ) +{ + XP_U16 ii; + for ( ii = 0; ii < N_RESIZE_FONTS; ++ii ) { + if ( !!dctx->fcEntry[ii].setFont ) { + DeleteObject( dctx->fcEntry[ii].setFont ); + } + } + XP_MEMSET( &dctx->fcEntry, 0, sizeof(dctx->fcEntry) ); +} + +#ifdef MEASURE_OLD_WAY static void ceMeasureTextHt( HFONT font, const char* str, XP_U16* ht, XP_U16* offset ) { @@ -325,27 +491,121 @@ ceMeasureTextHt( HFONT font, const char* str, XP_U16* ht, XP_U16* offset ) *ht = lastLine - firstLine + 1; /* XP_LOGF( "%s(%s)=>ht: %d; offset: %d", __func__, str, *ht, *offset ); */ } /* ceMeasureTextHt */ +#endif static void -makeTestBuf( CEDrawCtx* XP_UNUSED(dctx), XP_UCHAR* buf, - XP_UCHAR XP_UNUSED(bufLen), RFIndex index ) +ceBestFitFont( CEDrawCtx* dctx, XP_U16 soughtHeight, RFIndex index, + FontCacheEntry* fce ) { - switch( index ) { - case RFONTS_TRAY: - case RFONTS_CELL: - /* build this from dictioanary */ - strcpy( buf, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ); - break; - case RFONTS_TRAYVAL: - strcpy( buf, "0" ); /* all numbers the same :-) */ - break; - case RFONTS_PTS: - strcpy( buf, "Pts:0?" ); - break; - case N_RESIZE_FONTS: - XP_ASSERT(0); +#ifdef MEASURE_OLD_WAY + HFONT font; + LOGFONT fontInfo; + char buf[65]; + XP_U16 offset; + XP_U16 actualHt; + XP_U16 trialHt; + +/* XP_LOGF( "%s: calculating for index: %d; height: %d", */ +/* __func__, index, height ); */ + + makeTestBuf( dctx, buf, VSIZE(buf), index ); + + for ( trialHt = soughtHeight; ; /*++trialHt*/ ) { + XP_MEMSET( &fontInfo, 0, sizeof(fontInfo) ); + fontInfo.lfHeight = trialHt; + HFONT testFont = CreateFontIndirect( &fontInfo ); + XP_U16 testOffset, testHt; + + /* XP_LOGF( "%s: looking for ht %d with testht %d", __func__, height, trialHt ); */ + ceMeasureTextHt( testFont, buf, &testHt, &testOffset ); + if ( testHt > soughtHeight ) { + /* we've gone too far, so choose last that fit!!! */ + XP_ASSERT( !!font ); + DeleteObject( testFont ); + break; + /* } else if ( trialHt == height /\* first time through *\/ */ + /* && testOffset > 0 ) { /\* for safety *\/ */ + /* trialHt += testOffset; */ + } else { + ++trialHt; + } + DeleteObject( font ); + font = testFont; + offset = testOffset; + actualHt = testHt; } -} + + if ( !!fce->setFont ) { + DeleteObject( fce->setFont ); + } + + fce->setFont = font; + fce->setFontHt = soughtHeight; + fce->offset = offset; + fce->actualHt = actualHt; +#else + wchar_t widebuf[65]; + XP_U16 len; + XP_U16 hasMinTop, hasMaxBottom; + XP_Bool firstPass; + HBRUSH white = dctx->brushes[CE_WHITE_COLOR]; + HDC memDC = CreateCompatibleDC( NULL ); + HBITMAP memBM; + XP_U16 testSize; + + XP_LOGF( "%s(index=%d)", __func__, index ); + + char sample[65]; + makeTestBuf( dctx, sample, VSIZE(sample), index ); + len = strlen(sample); + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, sample, len, + widebuf, len ); + + memBM = CreateCompatibleBitmap( memDC, 64, 64 ); + SelectObject( memDC, memBM ); + + for ( firstPass = XP_TRUE, testSize = soughtHeight*2; ; --testSize ) { + LOGFONT fontInfo; + XP_MEMSET( &fontInfo, 0, sizeof(fontInfo) ); + fontInfo.lfHeight = testSize; + HFONT testFont = CreateFontIndirect( &fontInfo ); + if ( !!testFont ) { + XP_U16 thisHeight, top, bottom; + + SelectObject( memDC, testFont ); + + /* first time, measure all of them to determine which chars have + high and low points */ + if ( firstPass ) { + ceMeasureGlyphs( dctx, memDC, widebuf, &hasMinTop, + &hasMaxBottom ); + firstPass = XP_FALSE; + } + /* Thereafter, just measure the two we know about */ + ceMeasureGlyph( memDC, white, sample[hasMinTop], 1000, 0, + &top, &bottom ); + ceMeasureGlyph( memDC, white, sample[hasMaxBottom], + top, bottom, &top, &bottom ); + thisHeight = bottom - top + 1; + + if ( thisHeight <= soughtHeight ) { /* got it!!! */ + fce->setFont = testFont; + fce->setFontHt = soughtHeight; + fce->offset = top; + fce->actualHt = thisHeight; + XP_LOGF( "Looking for %d; PICKED %d", + soughtHeight, thisHeight ); + break; + } + DeleteObject( testFont ); + XP_LOGF( "Looking for %d; rejected %d", soughtHeight, thisHeight ); + } + } + + DeleteObject( memBM ); + DeleteDC( memDC ); +#endif +} /* ceBestFitFont */ static HFONT ceGetSizedFont( CEDrawCtx* dctx, XP_U16 height, RFIndex index, @@ -354,51 +614,7 @@ ceGetSizedFont( CEDrawCtx* dctx, XP_U16 height, RFIndex index, FontCacheEntry* fce = &dctx->fcEntry[index]; if ( (0 != height) /* 0 means use what we have */ && fce->setFontHt != height ) { - HFONT font; - LOGFONT fontInfo; - char buf[65]; - XP_U16 offset; - XP_U16 actualHt; - XP_U16 trialHt; - - XP_LOGF( "%s: calculating for index: %d; height: %d", - __func__, index, height ); - - makeTestBuf( dctx, buf, VSIZE(buf), index ); - - for ( trialHt = height; ; /*++trialHt*/ ) { - XP_MEMSET( &fontInfo, 0, sizeof(fontInfo) ); - fontInfo.lfHeight = trialHt; - HFONT testFont = CreateFontIndirect( &fontInfo ); - XP_U16 testOffset, testHt; - -/* XP_LOGF( "%s: looking for ht %d with testht %d", __func__, height, trialHt ); */ - ceMeasureTextHt( testFont, buf, &testHt, &testOffset ); - if ( testHt > height ) { - /* we've gone too far, so choose last that fit!!! */ - XP_ASSERT( !!font ); - DeleteObject( testFont ); - break; -/* } else if ( trialHt == height /\* first time through *\/ */ -/* && testOffset > 0 ) { /\* for safety *\/ */ -/* trialHt += testOffset; */ - } else { - ++trialHt; - } - DeleteObject( font ); - font = testFont; - offset = testOffset; - actualHt = testHt; - } - - if ( !!fce->setFont ) { - DeleteObject( fce->setFont ); - } - - fce->setFont = font; - fce->setFontHt = height; - fce->offset = offset; - fce->actualHt = actualHt; + ceBestFitFont( dctx, height, index, fce ); } if ( !!offsetP ) { @@ -407,9 +623,9 @@ ceGetSizedFont( CEDrawCtx* dctx, XP_U16 height, RFIndex index, if ( !!actualHtP ) { *actualHtP = fce->actualHt; } - XP_ASSERT( !!fce->setFont ); + XP_ASSERT( !!fce->setFont ); /* failing... */ return fce->setFont; -} +} /* ceGetSizedFont */ #if 0 /* I'm trying to measure individual chars, but GetGlyphOutline and @@ -516,7 +732,7 @@ DRAW_FUNC_NAME(boardBegin)( DrawCtx* p_dctx, CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; CEAppGlobals* globals = dctx->globals; HDC hdc = globals->hdc; - XP_Bool canDraw = !!hdc; + XP_Bool canDraw = !!hdc && !!dctx->dict; if ( canDraw ) { dctx->prevBkColor = GetBkColor( hdc ); dctx->topFocus = dfs == DFS_TOP; @@ -722,7 +938,7 @@ DRAW_FUNC_NAME(trayBegin)( DrawCtx* p_dctx, const XP_Rect* XP_UNUSED(rect), CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; CEAppGlobals* globals = dctx->globals; HDC hdc = globals->hdc; - XP_Bool canDraw = !!hdc; + XP_Bool canDraw = !!hdc && !!dctx->dict; if ( canDraw ) { dctx->trayOwner = owner; dctx->topFocus = dfs == DFS_TOP; @@ -1352,11 +1568,7 @@ DRAW_FUNC_NAME(destroyCtxt)( DrawCtx* p_dctx ) DeleteObject( dctx->playerFont ); DeleteObject( dctx->selPlayerFont ); - for ( i = 0; i < N_RESIZE_FONTS; ++i ) { - if ( !!dctx->fcEntry[i].setFont ) { - DeleteObject( dctx->fcEntry[i].setFont ); - } - } + ceClearFontCache( dctx ); DeleteObject( dctx->rightArrow ); DeleteObject( dctx->downArrow ); diff --git a/xwords4/wince/cemain.c b/xwords4/wince/cemain.c index 67ee7ff97..bb1d0a9a7 100755 --- a/xwords4/wince/cemain.c +++ b/xwords4/wince/cemain.c @@ -418,6 +418,8 @@ hideScroller( CEAppGlobals* globals ) #endif typedef struct CEBoardParms { + XP_U16 scrnWidth; + XP_U16 boardHScale; XP_U16 boardVScale; XP_U16 boardTop; @@ -468,7 +470,7 @@ figureBoardParms( CEAppGlobals* globals, XP_U16 nRows, CEBoardParms* bparms ) { RECT rc; XP_U16 scrnWidth, scrnHeight; - XP_U16 trayVScale, boardLeft, scoreWidth, scoreHeight; + XP_U16 trayVScale, boardLeft, trayLeft, scoreWidth, scoreHeight; XP_U16 boardHt, boardWidth, hScale, vScale, nVisibleRows; XP_U16 trayTop, boardTop; XP_Bool horiz; @@ -511,6 +513,7 @@ figureBoardParms( CEAppGlobals* globals, XP_U16 nRows, CEBoardParms* bparms ) } boardWidth = scrnWidth - scrollWidth; + trayWidth = scrnWidth; if ( horiz ) { scoreWidth = scrnWidth; hScale = boardWidth / nRows; @@ -518,14 +521,16 @@ figureBoardParms( CEAppGlobals* globals, XP_U16 nRows, CEBoardParms* bparms ) /* center the board */ boardWidth += scrollWidth; boardLeft = (scrnWidth - boardWidth) / 2; /* center it all */ + trayLeft = 0; } else { /* move extra pixels into scoreboard */ hScale = (boardWidth - CE_MIN_SCORE_WIDTH) / nRows; boardWidth = hScale * nRows; scoreWidth = scrnWidth - boardWidth - scrollWidth; boardLeft = scoreWidth; + trayLeft = scoreWidth; + trayWidth -= scoreWidth; } - trayWidth = boardWidth + scrollWidth; trayTop = boardHt + scoreHeight + 1; trayVScale = scrnHeight - trayTop; @@ -551,6 +556,7 @@ figureBoardParms( CEAppGlobals* globals, XP_U16 nRows, CEBoardParms* bparms ) } } + bparms->scrnWidth = scrnWidth; bparms->boardHScale = hScale; bparms->boardVScale = vScale; bparms->boardTop = boardTop; @@ -558,7 +564,7 @@ figureBoardParms( CEAppGlobals* globals, XP_U16 nRows, CEBoardParms* bparms ) bparms->trayHeight = trayVScale; bparms->trayWidth = trayWidth; bparms->boardLeft = boardLeft; - bparms->trayLeft = boardLeft; + bparms->trayLeft = trayLeft; bparms->scoreWidth = scoreWidth; bparms->scoreHeight = scoreHeight; bparms->horiz = horiz; @@ -578,6 +584,30 @@ figureBoardParms( CEAppGlobals* globals, XP_U16 nRows, CEBoardParms* bparms ) #endif } /* figureBoardParms */ +static void +setOwnedRects( CEAppGlobals* globals, XP_U16 nRows, + const CEBoardParms* bparms ) +{ + if ( bparms->horiz ) { + RECT tmp; + + tmp.top = bparms->scoreHeight; /* Same for both */ + tmp.bottom = bparms->trayTop; /* Same for both */ + + tmp.left = 0; + tmp.right = bparms->boardLeft; + XP_MEMCPY( &globals->ownedRects[OWNED_RECT_LEFT], &tmp, + sizeof(globals->ownedRects[OWNED_RECT_LEFT]) ); + + tmp.left = tmp.right + (bparms->boardHScale * nRows); + tmp.right = bparms->scrnWidth; + XP_MEMCPY( &globals->ownedRects[OWNED_RECT_RIGHT], &tmp, + sizeof(globals->ownedRects[OWNED_RECT_RIGHT]) ); + } else { + XP_MEMSET( &globals->ownedRects, 0, sizeof(globals->ownedRects) ); + } +} /* setOwnedRects */ + static XP_Bool cePositionBoard( CEAppGlobals* globals ) { @@ -590,6 +620,7 @@ cePositionBoard( CEAppGlobals* globals ) XP_ASSERT( nCols <= CE_MAX_ROWS ); figureBoardParms( globals, nCols, &bparms ); + setOwnedRects( globals, nCols, &bparms ); if ( globals->gameInfo.timerEnabled ) { board_setTimerLoc( globals->game.board, bparms.timerLeft, @@ -668,6 +699,7 @@ ceInitAndStartBoard( CEAppGlobals* globals, XP_Bool newGame, const CommsAddrRec* XP_UNUSED_STANDALONE(addr) ) { DictionaryCtxt* dict; + DictionaryCtxt* toBeDestroyed = NULL; XP_UCHAR* newDictName = globals->gameInfo.dictName; /* This needs to happen even when !newGame because it's how the dict @@ -680,7 +712,7 @@ ceInitAndStartBoard( CEAppGlobals* globals, XP_Bool newGame, const XP_UCHAR* curDictName = dict_getName( dict ); if ( !!newDictName && ( !curDictName || 0 != strcmp( curDictName, newDictName ) ) ) { - dict_destroy( dict ); + toBeDestroyed = dict; dict = NULL; } else { replaceStringIfDifferent( globals->mpool, @@ -737,6 +769,10 @@ ceInitAndStartBoard( CEAppGlobals* globals, XP_Bool newGame, server_do( globals->game.server ); globals->isNewGame = FALSE; + + if ( !!toBeDestroyed ) { + dict_destroy( toBeDestroyed ); + } } /* ceInitAndStartBoard */ #ifdef DEBUG @@ -1385,7 +1421,14 @@ drawInsidePaint( CEAppGlobals* globals, const RECT* invalR ) globals->hdc = hdc; if ( !!invalR ) { - ce_draw_erase( globals->draw, invalR ); + XP_U16 ii; + for ( ii = 0; ii < N_OWNED_RECTS; ++ii ) { + RECT interR; + if ( IntersectRect( &interR, invalR, + &globals->ownedRects[ii] ) ) { + ce_draw_erase( globals->draw, &interR ); + } + } } board_draw( globals->game.board ); diff --git a/xwords4/wince/cemain.h b/xwords4/wince/cemain.h index c614f460f..3d3fd3dc9 100755 --- a/xwords4/wince/cemain.h +++ b/xwords4/wince/cemain.h @@ -85,7 +85,10 @@ typedef struct CEAppPrefs { XP_Bool fullScreen; } CEAppPrefs; -#define NUM_BUTTONS 4 +enum { OWNED_RECT_LEFT + ,OWNED_RECT_RIGHT + ,N_OWNED_RECTS +}; typedef struct CEAppGlobals { HINSTANCE hInst; @@ -95,8 +98,6 @@ typedef struct CEAppGlobals { HWND hwndCB; #endif - HWND buttons[NUM_BUTTONS]; - #ifdef _WIN32_WCE SHACTIVATEINFO sai; XW_WinceVersion winceVersion; @@ -121,6 +122,8 @@ typedef struct CEAppGlobals { void* timerClosures[NUM_TIMERS_PLUS_ONE]; XP_U32 timerWhens[NUM_TIMERS_PLUS_ONE]; + RECT ownedRects[N_OWNED_RECTS]; + XP_U16 flags; /* bits defined below */ #ifdef CEFEATURE_CANSCROLL @@ -160,26 +163,6 @@ enum { #define CE_NUM_EDITABLE_COLORS CE_BLACK_COLOR -typedef enum { - RFONTS_TRAY - ,RFONTS_TRAYVAL - ,RFONTS_CELL - ,RFONTS_PTS - - ,N_RESIZE_FONTS -} RFIndex; - -typedef struct _PenColorPair { - COLORREF ref; - HGDIOBJ pen; -} PenColorPair; - -typedef struct _FontCacheEntry { - HFONT setFont; - XP_U16 setFontHt; - XP_U16 offset; - XP_U16 actualHt; -} FontCacheEntry; int messageBoxChar( CEAppGlobals* globals, XP_UCHAR* str, wchar_t* title, XP_U16 buttons );