I need to get the most suitable font for different language. So I could draw different language's text without using GDI text out API such as TextOut.
Actually, the api TextOut does it.
HFONT hFont = NULL;
LOGFONT lg = {0};
lg.lfHeight = 14;
lg.lfWeight = FW_NORMAL;
wcscpy_s(lg.lfFaceName, LF_FACESIZE ,L"Arial");
hFont = CreateFontIndirect(&lg);
SelectObject(hdc,hFont);
TextOut(hdc, 0, 50, L"abc我爱", 5);
Because the Arial font does not support Chinese, TextOut should not be able to draw the Chinese '我爱'. But it does and choose a suitable font, a normal font rather than some kind of art font, for it.
How could I simulate what TextOut does, or is there any other way to figure out the best suitable font for one language under Windows?
Yes, it is possible to do this. Here is the slight modified code snippet from chrome project,
// Callback to |EnumEnhMetaFile()| to intercept font creation.
int CALLBACK MetaFileEnumProc(HDC hdc,
HANDLETABLE* table,
CONST ENHMETARECORD* record,
int table_entries,
LPARAM log_font)
{
if (record->iType == EMR_EXTCREATEFONTINDIRECTW) {
const EMREXTCREATEFONTINDIRECTW* create_font_record =
reinterpret_cast<const EMREXTCREATEFONTINDIRECTW*>(record);
*reinterpret_cast<LOGFONT*>(log_font) = create_font_record->elfw.elfLogFont;
}
return 1;
}
// Finds a fallback font to use to render the specified |text| with respect to
// an initial |font|. Returns the resulting font via out param |result|. Returns
// |true| if a fallback font was found.
// Adapted from WebKit's |FontCache::GetFontDataForCharacters()|.
// TODO(asvitkine): This should be moved to font_fallback_win.cc.
bool ChooseFallbackFont(HDC hdc,
HFONT font,
const wchar_t* text,
int text_length,
LOGFONT* result)
{
// Use a meta file to intercept the fallback font chosen by Uniscribe.
HDC meta_file_dc = CreateEnhMetaFile(hdc, NULL, NULL, NULL);
if (!meta_file_dc)
return false;
if (font)
SelectObject(meta_file_dc, font);
SCRIPT_STRING_ANALYSIS script_analysis;
HRESULT hresult =
ScriptStringAnalyse(meta_file_dc, text, text_length, 0, -1,
SSA_METAFILE | SSA_FALLBACK | SSA_GLYPHS | SSA_LINK,
0, NULL, NULL, NULL, NULL, NULL, &script_analysis);
if (SUCCEEDED(hresult)) {
hresult = ScriptStringOut(script_analysis, 0, 0, 0, NULL, 0, 0, FALSE);
ScriptStringFree(&script_analysis);
}
bool found_fallback = false;
HENHMETAFILE meta_file = CloseEnhMetaFile(meta_file_dc);
if (SUCCEEDED(hresult)) {
LOGFONT log_font;
log_font.lfFaceName[0] = 0;
EnumEnhMetaFile(0, meta_file, MetaFileEnumProc, &log_font, NULL);
if (log_font.lfFaceName[0]) {
*result = log_font;
found_fallback = true;
}
}
DeleteEnhMetaFile(meta_file);
return found_fallback;
}
Sample code:
std::wstring arabicStr = L"ششش";
hdc = BeginPaint(hWnd, &ps);
LOGFONT logFont = {0};
wcscpy_s(logFont.lfFaceName, L"微软雅黑");
HFONT hFont = CreateFontIndirect(&logFont);
ChooseFallbackFont(hdc, hFont, arabicStr.c_str(), arabicStr.length(), &logFont);
ATLTRACE(logFont.lfFaceName);
HFONT hFontNew = CreateFontIndirect(&logFont);
HFONT hFontOld = (HFONT)SelectObject(hdc, hFontNew);
wchar_t glyphs[10] = {0};
GCP_RESULTS gcpRet = {0};
gcpRet.lStructSize = sizeof(gcpRet);
gcpRet.lpGlyphs = glyphs;
gcpRet.nGlyphs = 10;
ATLASSERT(GetCharacterPlacement(hdc, arabicStr.c_str(), arabicStr.length(), GCP_MAXEXTENT, &gcpRet,
GCP_DISPLAYZWG | GCP_GLYPHSHAPE | GCP_REORDER ));
RECT rcClient;
GetClientRect(hWnd, &rcClient);
ExtTextOut(hdc, 200, 200, ETO_GLYPH_INDEX | ETO_RTLREADING, &rcClient, gcpRet.lpGlyphs, gcpRet.nGlyphs, NULL);
SelectObject(hdc, hFontOld);
DeleteObject(hFontNew);
DeleteObject(hFont);
EndPaint(hWnd, &ps);
微软雅黑
is a font which does not support arabic, so we need a fallback font for the arabic. In my box, GDI automatically choose Microsoft Sans Serif
for the arabic.
I've had to do this. It's possible but it's a lot of work.
Windows actually uses multiple schemes: Uniscribe, uses font fallback, which relies on internal knowledge of which fonts cover which scripts. Higher level APIs, like GDI's, use font-linking, which relies on lists of fonts stashed in the registry.
The code I was extending relied on Uniscribe to break the text into runs of identical scripts. It then checked the selected font to see if it could cover all the glyphs in a given run.
If it couldn't, then it checked the font-linking fields in the Registry. If those weren't there or if there wasn't a list for the preferred font, it enumerated all the fonts to find candidates that covered the needed scripts (caching bitmasks that encoded which scripts were covered for each font encountered).
Those candidates were then scored (ranked) based on actual coverage for the run (how many glyphs were still missing). From the high-ranked candidates, it selected one that was "most similar" to the preferred font. I think that part was based on the font's PANOSE number, but it's been a while, so I don't remember clearly. It might have just been some heuristics that looked at font weight and similar parameters.
There was also a hack to try to minimize the number of font switches, on the theory that it was better to use one backup font that covers the entire text than to switch fonts several times. As I recall, this solved some problems where strings of CJK characters would pick some glyphs from a Chinese font and others from a Japanese font. To native readers, it was better to have the entire string in one typeface when possible, even if the preferred font was in the other style.