Files
GTASource/rage/scaleform/Src/GFxPlayer/GFxFontProviderWin32.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

765 lines
27 KiB
C++

/**********************************************************************
Filename : GFxFontProviderWin32.h
Content : Win32 API Font provider (GetGlyphOutline)
Created : 6/21/2007
Authors : Maxim Shemanarev
Copyright : (c) 2001-2007 Scaleform Corp. All Rights Reserved.
Notes :
Licensees may use this file in accordance with the valid Scaleform
Commercial License Agreement provided with the software.
This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE.
----------------------------------------------------------------------
The code of these classes was taken from the Anti-Grain Geometry
Project and modified for the use by Scaleform.
Permission to use without restrictions is hereby granted to
Scaleform Corporation by the author of Anti-Grain Geometry Project.
See http://antigtain.com for details.
**********************************************************************/
#include "GFxFontProviderWin32.h"
#include "GFxString.h"
#include "GFxShape.h"
#include "GStd.h"
//------------------------------------------------------------------------
struct GFxGdiSelectObjectGuard
{
HDC WinDC;
HGDIOBJ WinObj;
GFxGdiSelectObjectGuard(HDC dc, HGDIOBJ obj) : WinDC(dc), WinObj(::SelectObject(dc, obj)) {}
~GFxGdiSelectObjectGuard() { ::SelectObject(WinDC, WinObj); }
};
//------------------------------------------------------------------------
GFxExternalFontWin32::~GFxExternalFontWin32()
{
if (HintedFont)
::DeleteObject(HintedFont);
if (MasterFont)
::DeleteObject(MasterFont);
}
//------------------------------------------------------------------------
int CALLBACK EnumFontFamExProc(
ENUMLOGFONTEXW *lpelfe, // logical-font data
NEWTEXTMETRICEXW *lpntme, // physical-font data
DWORD FontType, // type of font
LPARAM lParam // application-defined data
)
{
GUNUSED3(lpelfe,lpntme,FontType);
bool* found = (bool*)lParam;
*found = true;
return 0;
}
GFxExternalFontWin32::GFxExternalFontWin32(GFxFontProviderWin32 *pprovider,
GFxFontSysDataWin32* sysData,
const char* name,
UInt fontFlags)
: GFxFont(fontFlags),
pFontProvider(pprovider),
pSysData(sysData),
MasterFont(0), HintedFont(0), LastHintedFontSize(0),
Scale1024(1024.0f / Float(GGO_FontHeight))
{
Name.Resize(G_strlen(name) + 1);
G_strcpy(&Name[0], G_strlen(name) + 1, name);
// Font name is encoded as UTF8, so unpack.
NameW.Resize(GUTF8Util::GetLength(name) + 1);
GUTF8Util::DecodeString(&NameW[0], name);
LOGFONTW lf;
lstrcpyW((LPWSTR)&lf.lfFaceName, &NameW[0]);
lf.lfCharSet = DEFAULT_CHARSET;
bool found = false;
EnumFontFamiliesExW(pSysData->WinHDC, &lf, (FONTENUMPROCW)EnumFontFamExProc, (LPARAM)&found, 0);
if (!found)
return;
MasterFont = ::CreateFontW(-GGO_FontHeight, // height of font
0, // average character width
0, // angle of escapement
0, // base-line orientation angle
IsBold() ? FW_BOLD : FW_NORMAL, // font weight
IsItalic(), // italic attribute option
0, // underline attribute option
0, // strikeout attribute option
DEFAULT_CHARSET, // character set identifier
OUT_DEFAULT_PRECIS, // output precision
CLIP_DEFAULT_PRECIS, // clipping precision
ANTIALIASED_QUALITY, // output quality
DEFAULT_PITCH, // pitch and family
&NameW[0]); // typeface name
if(MasterFont)
{
TEXTMETRICW tm;
GFxGdiSelectObjectGuard g1(pSysData->WinHDC, MasterFont);
::GetTextMetricsW(pSysData->WinHDC, &tm);
SetFontMetrics(Float(tm.tmExternalLeading) * Scale1024,
Float(tm.tmAscent) * Scale1024,
Float(tm.tmDescent) * Scale1024);
loadKerningPairs();
}
}
//------------------------------------------------------------------------
void GFxExternalFontWin32::SetHinting(const GFxFont::NativeHintingType& pnh)
{
Hinting.MaxRasterHintedSize = pnh.MaxRasterHintedSize;
Hinting.MaxVectorHintedSize = pnh.MaxVectorHintedSize;
Hinting.RasterRange = pnh.RasterRange;
Hinting.VectorRange = pnh.VectorRange;
}
//------------------------------------------------------------------------
void GFxExternalFontWin32::loadKerningPairs()
{
GArray<KERNINGPAIR> pairs;
UInt size = ::GetKerningPairsW(pSysData->WinHDC, 0, 0);
if(size)
{
pairs.Resize(size);
::GetKerningPairsW(pSysData->WinHDC, size, &pairs[0]);
}
KerningPairs.Clear();
for(UInt i = 0; i < pairs.GetSize(); ++i)
{
KerningPairType pair;
pair.Char0 = pairs[i].wFirst;
pair.Char1 = pairs[i].wSecond;
KerningPairs.Add(pair, Float(pairs[i].iKernAmount) * Scale1024);
}
}
//------------------------------------------------------------------------
bool GFxExternalFontWin32::decomposeGlyphOutline(const UByte* data,
UInt size,
GFxShapeNoStyles* shape)
{
const UByte* curGlyph = data;
const UByte* endGlyph = data + size;
bool hinted = shape->GetHintedGlyphSize() != 0;
GFxPathPacker path;
bool shapeValid = false;
while(curGlyph < endGlyph)
{
const TTPOLYGONHEADER* th = (TTPOLYGONHEADER*)curGlyph;
const UByte* endPoly = curGlyph + th->cb;
const UByte* curPoly = curGlyph + sizeof(TTPOLYGONHEADER);
path.Reset();
path.SetFill0(1);
path.SetFill1(0);
if (hinted)
path.SetMoveTo(FxToTwips(th->pfxStart.x), -FxToTwips(th->pfxStart.y));
else
path.SetMoveTo(FxToS1024(th->pfxStart.x), -FxToS1024(th->pfxStart.y));
while(curPoly < endPoly)
{
const TTPOLYCURVE* pc = (const TTPOLYCURVE*)curPoly;
if (pc->wType == TT_PRIM_LINE)
{
int i;
for (i = 0; i < pc->cpfx; i++)
{
if (hinted)
path.LineToAbs(FxToTwips(pc->apfx[i].x), -FxToTwips(pc->apfx[i].y));
else
path.LineToAbs(FxToS1024(pc->apfx[i].x), -FxToS1024(pc->apfx[i].y));
}
}
if (pc->wType == TT_PRIM_QSPLINE)
{
int u;
for (u = 0; u < pc->cpfx - 1; u++) // Walk through points in spline
{
POINTFX pntB = pc->apfx[u]; // B is always the current point
POINTFX pntC = pc->apfx[u+1];
if (u < pc->cpfx - 2) // If not on last spline, compute C
{
// midpoint (x,y)
*(int*)&pntC.x = (*(int*)&pntB.x + *(int*)&pntC.x) / 2;
*(int*)&pntC.y = (*(int*)&pntB.y + *(int*)&pntC.y) / 2;
}
if (hinted)
path.CurveToAbs(FxToTwips(pntB.x), -FxToTwips(pntB.y),
FxToTwips(pntC.x), -FxToTwips(pntC.y));
else
path.CurveToAbs(FxToS1024(pntB.x), -FxToS1024(pntB.y),
FxToS1024(pntC.x), -FxToS1024(pntC.y));
}
}
curPoly += sizeof(WORD) * 2 + sizeof(POINTFX) * pc->cpfx;
}
curGlyph += th->cb;
if (!path.IsEmpty())
{
path.ClosePath();
shape->AddPath(&path);
shapeValid = true;
}
}
return shapeValid;
}
//------------------------------------------------------------------------
class GFxBitsetIterator
{
public:
GFxBitsetIterator(const UByte* bits, unsigned offset = 0) :
Bits(bits + (offset >> 3)),
Mask(UByte(0x80 >> (offset & 7)))
{}
void operator ++ ()
{
Mask >>= 1;
if(Mask == 0)
{
++Bits;
Mask = 0x80;
}
}
unsigned GetBit() const
{
return (*Bits) & Mask;
}
private:
const UByte* Bits;
UByte Mask;
};
//------------------------------------------------------------------------
void GFxExternalFontWin32::decomposeGlyphBitmap(const UByte* data,
int w, int h,
int x, int y,
GFxGlyphRaster* raster)
{
raster->Width = w;
raster->Height = h;
raster->OriginX = -x;
raster->OriginY = y;
raster->Raster.Resize(w * h);
int i, pitch;
const UByte* src = data;
UByte* dst = &raster->Raster[0];
if (pSysData->RasterFormat == GGO_BITMAP)
{
pitch = ((w + 31) >> 5) << 2;
for(i = 0; i < h; i++)
{
GFxBitsetIterator bits(src, 0);
int j;
for(j = 0; j < w; j++)
{
*dst++ = bits.GetBit() ? 255 : 0;
++bits;
}
src += pitch;
}
}
else
{
pitch = ((w + 3) >> 2) << 2;
for(i = 0; i < h; i++)
{
int j;
for(j = 0; j < w; j++)
{
*dst++ = pSysData->Gamma[src[j]];
}
src += pitch;
}
}
}
//------------------------------------------------------------------------
int GFxExternalFontWin32::GetGlyphIndex(UInt16 code) const
{
if (MasterFont)
{
const UInt* indexPtr = CodeTable.Get(code);
if (indexPtr)
return *indexPtr;
GFxGdiSelectObjectGuard g1(pSysData->WinHDC, MasterFont);
GLYPHMETRICS gm;
MAT2 im = { {0,1}, {0,0}, {0,0}, {0,1} };
int ret = ::GetGlyphOutlineW(pSysData->WinHDC,
code,
GGO_METRICS,
&gm,
0, 0,
&im);
if (ret == GDI_ERROR)
return -1;
GlyphType glyph;
glyph.Code = code;
glyph.Advance = Float(gm.gmCellIncX) * Scale1024;
glyph.Bounds.Left = Float(gm.gmptGlyphOrigin.x) * Scale1024;
glyph.Bounds.Top = -Float(gm.gmptGlyphOrigin.y) * Scale1024;
glyph.Bounds.Right = glyph.Bounds.Left + Float(gm.gmBlackBoxX) * Scale1024;
glyph.Bounds.Bottom = glyph.Bounds.Top + Float(gm.gmBlackBoxY) * Scale1024;
GFxExternalFontWin32* pthis = const_cast<GFxExternalFontWin32*>(this);
pthis->Glyphs.PushBack(glyph);
pthis->CodeTable.Add(code, (UInt)Glyphs.GetSize()-1);
return (UInt)Glyphs.GetSize()-1;
}
return -1;
}
//------------------------------------------------------------------------
bool GFxExternalFontWin32::IsHintedVectorGlyph(UInt glyphIndex, UInt glyphSize) const
{
if (glyphIndex == (UInt)-1 ||
Hinting.VectorRange == DontHint ||
glyphSize > Hinting.MaxVectorHintedSize)
{
return false;
}
if (Hinting.VectorRange == HintAll)
return true;
return IsCJK(UInt16(Glyphs[glyphIndex].Code));
}
//------------------------------------------------------------------------
bool GFxExternalFontWin32::IsHintedRasterGlyph(UInt glyphIndex, UInt glyphSize) const
{
if (glyphIndex == (UInt)-1 ||
Hinting.RasterRange == DontHint ||
glyphSize > Hinting.MaxRasterHintedSize)
{
return false;
}
if (Hinting.RasterRange == HintAll)
return true;
return IsCJK(UInt16(Glyphs[glyphIndex].Code));
}
//------------------------------------------------------------------------
GFxShapeBase* GFxExternalFontWin32::GetGlyphShape(UInt glyphIndex,
UInt glyphSize)
{
if (glyphIndex == (UInt)-1)
return 0;
if (!IsHintedVectorGlyph(glyphIndex, glyphSize))
glyphSize = 0;
GlyphType& glyph = Glyphs[glyphIndex];
HFONT hFont = MasterFont;
if (glyphSize)
{
if (glyphSize != LastHintedFontSize)
{
if (HintedFont)
::DeleteObject(HintedFont);
HintedFont = ::CreateFontW(-int(glyphSize), // height of font
0, // average character width
0, // angle of escapement
0, // base-line orientation angle
IsBold() ? FW_BOLD : FW_NORMAL, // font weight
IsItalic(), // italic attribute option
0, // underline attribute option
0, // strikeout attribute option
DEFAULT_CHARSET, // character set identifier
OUT_DEFAULT_PRECIS, // output precision
CLIP_DEFAULT_PRECIS, // clipping precision
ANTIALIASED_QUALITY, // output quality
DEFAULT_PITCH, // pitch and family
&NameW[0]); // typeface name
LastHintedFontSize = glyphSize;
}
hFont = HintedFont;
}
GFxGdiSelectObjectGuard g1(pSysData->WinHDC, hFont);
if (pSysData->GlyphBuffer.GetSize() == 0)
pSysData->GlyphBuffer.Resize(GFxFontSysDataWin32::BufSizeInc);
#ifndef GGO_UNHINTED // For compatibility with old SDKs.
#define GGO_UNHINTED 0x0100
#endif
GLYPHMETRICS gm;
int totalSize = 0;
MAT2 im = { {0,1}, {0,0}, {0,0}, {0,1} };
totalSize = ::GetGlyphOutlineW(pSysData->WinHDC,
glyph.Code,
GGO_NATIVE,
&gm,
(DWORD)pSysData->GlyphBuffer.GetSize(),
&pSysData->GlyphBuffer[0],
&im);
if (totalSize == GDI_ERROR ||
totalSize > (int)pSysData->GlyphBuffer.GetSize())
{
// An error has occured. Most probably it's because
// of not enough room in the buffer. So, request the buffer size,
// reallocate, and try again. The second call may fail for
// some other reason (no glyph); in this case there's nothing
// else to do, just return NULL.
// The buffer is reallocated with adding BufSizeInc in order
// to reduce the number of reallocations.
//----------------------------------
totalSize = ::GetGlyphOutlineW(pSysData->WinHDC,
glyph.Code,
GGO_NATIVE,
&gm, 0, 0, &im);
if(totalSize == GDI_ERROR) return 0;
pSysData->GlyphBuffer.Resize(totalSize + GFxFontSysDataWin32::BufSizeInc);
totalSize = ::GetGlyphOutlineW(pSysData->WinHDC,
glyph.Code,
GGO_NATIVE,
&gm,
(DWORD)pSysData->GlyphBuffer.GetSize(),
&pSysData->GlyphBuffer[0],
&im);
if (totalSize == GDI_ERROR ||
totalSize > (int)pSysData->GlyphBuffer.GetSize())
return 0;
}
GFxShapeNoStyles* pshape = 0;
if (totalSize)
{
pshape = new GFxShapeNoStyles(ShapePageSize);
pshape->SetHintedGlyphSize(glyphSize);
if (!decomposeGlyphOutline(&pSysData->GlyphBuffer[0], totalSize, pshape))
{
pshape->Release();
pshape = 0;
}
}
return pshape;
}
//------------------------------------------------------------------------
GFxGlyphRaster* GFxExternalFontWin32::GetGlyphRaster(UInt glyphIndex, UInt glyphSize)
{
if (!IsHintedRasterGlyph(glyphIndex, glyphSize))
return 0;
GlyphType& glyph = Glyphs[glyphIndex];
if (glyphSize != LastHintedFontSize)
{
if (HintedFont)
::DeleteObject(HintedFont);
HintedFont = ::CreateFontW(-int(glyphSize), // height of font
0, // average character width
0, // angle of escapement
0, // base-line orientation angle
IsBold() ? FW_BOLD : FW_NORMAL, // font weight
IsItalic(), // italic attribute option
0, // underline attribute option
0, // strikeout attribute option
DEFAULT_CHARSET, // character set identifier
OUT_DEFAULT_PRECIS, // output precision
CLIP_DEFAULT_PRECIS, // clipping precision
ANTIALIASED_QUALITY, // output quality
DEFAULT_PITCH, // pitch and family
&NameW[0]); // typeface name
LastHintedFontSize = glyphSize;
}
GFxGdiSelectObjectGuard g1(pSysData->WinHDC, HintedFont);
if (pSysData->GlyphBuffer.GetSize() == 0)
pSysData->GlyphBuffer.Resize(GFxFontSysDataWin32::BufSizeInc);
GLYPHMETRICS gm;
int totalSize = 0;
MAT2 im = { {0,1}, {0,0}, {0,0}, {0,1} };
totalSize = ::GetGlyphOutlineW(pSysData->WinHDC,
glyph.Code,
pSysData->RasterFormat,
&gm,
(DWORD)pSysData->GlyphBuffer.GetSize(),
&pSysData->GlyphBuffer[0],
&im);
if (totalSize == GDI_ERROR ||
totalSize > (int)pSysData->GlyphBuffer.GetSize())
{
// An error has occured. Most probably it's because
// of not enough room in the buffer. So, request the buffer size,
// reallocate, and try again. The second call may fail for
// some other reason (no glyph); in this case there's nothing
// else to do, just return NULL.
// The buffer is reallocated with adding BufSizeInc in order
// to reduce the number of reallocations.
//----------------------------------
totalSize = ::GetGlyphOutlineW(pSysData->WinHDC,
glyph.Code,
pSysData->RasterFormat,
&gm, 0, 0, &im);
if(totalSize == GDI_ERROR) return 0;
pSysData->GlyphBuffer.Resize(totalSize + GFxFontSysDataWin32::BufSizeInc);
totalSize = ::GetGlyphOutlineW(pSysData->WinHDC,
glyph.Code,
pSysData->RasterFormat,
&gm,
(DWORD)pSysData->GlyphBuffer.GetSize(),
&pSysData->GlyphBuffer[0],
&im);
if (totalSize == GDI_ERROR ||
totalSize > (int)pSysData->GlyphBuffer.GetSize())
return 0;
}
if (pRaster.GetPtr() == 0)
pRaster = *new GFxGlyphRaster;
if (totalSize)
{
decomposeGlyphBitmap(&pSysData->GlyphBuffer[0],
gm.gmBlackBoxX,
gm.gmBlackBoxY,
gm.gmptGlyphOrigin.x,
gm.gmptGlyphOrigin.y,
pRaster);
return pRaster;
}
else
{
pRaster->Width = 1;
pRaster->Height = 1;
pRaster->Raster.Resize(1);
pRaster->Raster[0] = 0;
}
return pRaster;
}
//------------------------------------------------------------------------
Float GFxExternalFontWin32::GetAdvance(UInt glyphIndex) const
{
if (glyphIndex == (UInt)-1)
return GetDefaultGlyphWidth();
return Glyphs[glyphIndex].Advance;
}
//------------------------------------------------------------------------
Float GFxExternalFontWin32::GetKerningAdjustment(UInt lastCode, UInt thisCode) const
{
Float adjustment;
KerningPairType k;
k.Char0 = (UInt16)lastCode;
k.Char1 = (UInt16)thisCode;
if (KerningPairs.Get(k, &adjustment))
{
return adjustment;
}
return 0;
}
//------------------------------------------------------------------------
Float GFxExternalFontWin32::GetGlyphWidth(UInt glyphIndex) const
{
if (glyphIndex == (UInt)-1)
return GetDefaultGlyphWidth();
const GRectF& r = Glyphs[glyphIndex].Bounds;
return r.Width();
}
//------------------------------------------------------------------------
Float GFxExternalFontWin32::GetGlyphHeight(UInt glyphIndex) const
{
if (glyphIndex == (UInt)-1)
return GetDefaultGlyphHeight();
const GRectF& r = Glyphs[glyphIndex].Bounds;
return r.Height();
}
//------------------------------------------------------------------------
GRectF& GFxExternalFontWin32::GetGlyphBounds(UInt glyphIndex, GRectF* prect) const
{
if (glyphIndex == (UInt)-1)
prect->SetRect(GetDefaultGlyphWidth(), GetDefaultGlyphHeight());
else
*prect = Glyphs[glyphIndex].Bounds;
return *prect;
}
//------------------------------------------------------------------------
const char* GFxExternalFontWin32::GetName() const
{
return &Name[0];
}
//------------------------------------------------------------------------
GFxFontProviderWin32::GFxFontProviderWin32(HDC dc):
SysData(dc)
{
GFxFont::NativeHintingType nhAllFonts;
nhAllFonts.RasterRange = GFxFont::HintCJK;
nhAllFonts.VectorRange = GFxFont::DontHint;
nhAllFonts.MaxRasterHintedSize = 24;
nhAllFonts.MaxVectorHintedSize = 24;
NativeHinting.PushBack(nhAllFonts);
}
//------------------------------------------------------------------------
GFxFontProviderWin32::~GFxFontProviderWin32()
{
}
//------------------------------------------------------------------------
GFxFont::NativeHintingType* GFxFontProviderWin32::findNativeHinting(const char* name)
{
UInt i;
for (i = 0; i < NativeHinting.GetSize(); ++i)
{
if (NativeHinting[i].Typeface.CompareNoCase(name) == 0)
return &NativeHinting[i];
}
return 0;
}
//------------------------------------------------------------------------
void GFxFontProviderWin32::SetHinting(const char* name,
GFxFont::NativeHintingRange vectorRange,
GFxFont::NativeHintingRange rasterRange,
UInt maxVectorHintedSize,
UInt maxRasterHintedSize)
{
GFxFont::NativeHintingType* pnh = findNativeHinting(name);
if (pnh == 0)
{
GFxFont::NativeHintingType nh = NativeHinting[0];
nh.Typeface = name;
NativeHinting.PushBack(nh);
pnh = &NativeHinting[NativeHinting.GetSize()-1];
}
pnh->VectorRange = vectorRange;
pnh->RasterRange = rasterRange;
pnh->MaxVectorHintedSize = maxVectorHintedSize;
pnh->MaxRasterHintedSize = maxRasterHintedSize;
}
//------------------------------------------------------------------------
void GFxFontProviderWin32::SetHintingAllFonts(GFxFont::NativeHintingRange vectorRange,
GFxFont::NativeHintingRange rasterRange,
UInt maxVectorHintedSize,
UInt maxRasterHintedSize)
{
NativeHinting[0].VectorRange = vectorRange;
NativeHinting[0].RasterRange = rasterRange;
NativeHinting[0].MaxVectorHintedSize = maxVectorHintedSize;
NativeHinting[0].MaxRasterHintedSize = maxRasterHintedSize;
}
//------------------------------------------------------------------------
void GFxFontProviderWin32::SetRasterFormat(UInt format, const UByte* gamma)
{
SysData.RasterFormat = format;
if (gamma)
{
switch(format)
{
case GGO_GRAY2_BITMAP: memcpy(SysData.Gamma, gamma, 5); break;
case GGO_GRAY4_BITMAP: memcpy(SysData.Gamma, gamma, 17); break;
case GGO_GRAY8_BITMAP: memcpy(SysData.Gamma, gamma, 65); break;
}
}
}
//------------------------------------------------------------------------
GFxFont* GFxFontProviderWin32::CreateFont(const char* name, UInt fontFlags)
{
//if (fontFlags & GFxFont::FF_BoldItalic) return 0; // DBG
// Mask flags to be safe.
fontFlags &= GFxFont::FF_CreateFont_Mask;
fontFlags |= GFxFont::FF_DeviceFont | GFxFont::FF_NativeHinting;
// Return a newly created font with a default RefCount of 1.
// It is users responsibility to cache the font and release
// it when it is no longer necessary.
GFxExternalFontWin32* pnewFont =
new GFxExternalFontWin32(this, &SysData, name, fontFlags);
if (pnewFont && !pnewFont->IsValid())
{
pnewFont->Release();
return 0;
}
GFxFont::NativeHintingType* pnh = findNativeHinting(name);
if (pnh)
pnewFont->SetHinting(*pnh);
else
pnewFont->SetHinting(NativeHinting[0]);
return pnewFont;
}
//------------------------------------------------------------------------
static int CALLBACK LoadFontNamesProc(
ENUMLOGFONTEXW *lpelfe, // logical-font data
NEWTEXTMETRICEXW*, // physical-font data
DWORD, // type of font
LPARAM lParam // application-defined data
)
{
GStringHash<GString>* pfontnames = static_cast<GStringHash<GString>*>((void*)lParam);
GString fontname(lpelfe->elfFullName);
pfontnames->Set(fontname, fontname);
return 1;
}
void GFxFontProviderWin32::LoadFontNames(GStringHash<GString>& fontnames)
{
LOGFONTW lf;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfFaceName[0] = '\0';
lf.lfPitchAndFamily = 0;
EnumFontFamiliesExW(SysData.WinHDC, &lf, (FONTENUMPROCW)LoadFontNamesProc, (LPARAM)&fontnames, 0);
}