如何自动选择最适合不同语言的字体?

时间:2013-05-30 05:50:53

标签: windows encoding fonts gdi

我需要为不同的语言获取最合适的字体。所以我可以在不使用GDI文本输出API的情况下绘制不同语言的文本,例如TextOut。

实际上,api TextOut就是这么做的。

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

因为Arial字体不支持中文,所以TextOut不能绘制中文'我爱'。但它确实并选择了合适的字体,普通字体而不是某种艺术字体。

我如何模拟TextOut的功能,还是有其他方法可以为Windows下的一种语言找出最合适的字体?

2 个答案:

答案 0 :(得分:2)

是的,可以这样做。以下是chrome项目的轻微修改代码片段,

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

示例代码:

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

微软雅黑是一种不支持阿拉伯语的字体,因此我们需要阿拉伯语的后备字体。在我的框中,GDI会自动为阿拉伯语选择Microsoft Sans Serif

答案 1 :(得分:2)

我必须这样做。这是可能的,但它做了很多工作。

Windows实际上使用多种方案:Uniscribe,使用字体回退,它依赖于哪些字体涵盖哪些脚本的内部知识。更高级别的API,如GDI,使用字体链接,它依赖于注册表中隐藏的字体列表。

我扩展的代码依赖于Uniscribe将文本分解为相同脚本的运行。然后检查所选字体以查看它是否可以覆盖给定运行中的所有字形。

如果不能,则检查注册表中的字体链接字段。如果那些不存在或者没有首选字体的列表,它会枚举所有字体以查找涵盖所需脚本的候选者(缓存编码了遇到的每种字体覆盖哪些脚本的位掩码)。

然后根据跑步的实际覆盖范围对这些候选人进行评分(排名)(仍然缺少多少个字形)。从排名较高的候选人中,它选择了一个最相似的候选人#34;到首选字体。我认为那部分是基于字体的PANOSE编号,但它已经有一段时间了,所以我不记得清楚了。可能只是一些启发式方法来查看字体粗细和类似参数。

还有一个黑客尝试最小化字体开关的数量,理论上最好使用覆盖整个文本的一个备份字体而不是多次切换字体。我记得,这解决了一些问题,其中CJK字符串将从中文字体中选择一些字形,而从日语字体中选择其他字形。对于本地读者来说,最好尽可能将整个字符串放在一个字体中,即使首选字体是另一种字体。