【问题标题】:Check if Unicode character is displayed or tofu检查是否显示Unicode字符或豆腐
【发布时间】:2018-05-30 03:54:48
【问题描述】:

我的问题与this one 类似,但有点进步。

在我的 Win32 程序中,我在 BMP 上方有一些带有 Unicode 字符的菜单按钮,例如 U+1F5A4 ???? (UTF-16 代理对 0xD83D 0xDDA4)。
在 Windows 10 中,系统字体 Segoe UI 没有此字形:它会自动替换为字体 Segoe UI Symbol 中的字形并正确显示在按钮中,谢谢到一个称为字体链接(或字体回退,我仍然不清楚)的过程。
但在 Windows 7 中,字体链接带来的字体也没有此字形,并且代理对显示为两个空框▯▯。在带有 Tahoma 字体的 Windows XP 中也是如此。

我想避免这些替换框,方法是在分配给按钮之前或之后解析文本,并用一些常见的 ASCII 字符替换丢失的字形。

我尝试了GetGlyphOutlineScriptGetCMapGetFontUnicodeRangesGetGlyphIndices,但它们不支持代理对。
我还尝试了支持代理对的GetCharacterPlacement 和 Uniscribe ScriptItemize+ScriptShape,但所有这些函数只搜索 HDC 的基本字体(Segoe UI),他们不搜索最终后备字体(Segoe UI Symbol),它提供了 ????字形。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink 这是我看过的地方,但我真的认为它不在那里,系统会获取要链接的字体。

问题是:我如何知道系统字体链接是否生成了正确的字形或豆腐框?


编辑

我找到了一种复制 trom this code 并添加最后一个 GetCharacterPlacement 的解决方案。

#include <usp10.h>

wchar_t *checkGlyphExist( HWND hwnd, wchar_t *sUnicode, wchar_t *sLimited ) {

    // Create metafile
    HDC hdc = GetDC( hwnd );
    HDC metaFileDC = CreateEnhMetaFile( hdc, NULL, NULL, NULL );

    // Select menu font
    NONCLIENTMETRICSW ncm;
    ncm.cbSize = sizeof(ncm);
    SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0 );
    HFONT hFont = CreateFontIndirectW( &(ncm.lfMenuFont) );
    SelectObject( metaFileDC, hFont );
    wprintf( L"%s\n", ncm.lfMenuFont.lfFaceName );  // 'Segoe UI' in Win 10 and 7 (ok)
                                                    // 'Tahoma' in Win XP (ok)

    // Use the meta file to intercept the fallback font chosen by Uniscribe
    SCRIPT_STRING_ANALYSIS ssa;
    ScriptStringAnalyse( metaFileDC, sUnicode, wcslen(sUnicode), 0, -1,
                      SSA_METAFILE | SSA_FALLBACK | SSA_GLYPHS | SSA_LINK,  
                      0, NULL, NULL, NULL, NULL, NULL, &ssa );
    ScriptStringFree( &ssa );
    HENHMETAFILE metaFile = CloseEnhMetaFile(metaFileDC);
    LOGFONTW logFont = {0};
    EnumEnhMetaFile( 0, metaFile, metaFileEnumProc, &logFont, NULL );
    DeleteEnhMetaFile( metaFile );
    wprintf( L"%s\n", logFont.lfFaceName );
        // 'Segoe UI Symbol' in Win 10 (ok)
        // 'Microsoft Sans Serif' in Win 7 (wrong, should be 'Segoe UI Symbol')
        // 'Tahoma' in Win XP for characters above 0xFFFF (wrong, should be 'Microsoft Sans Serif', I guess)
    
    // Get glyph indices for the 'sUnicode' string
    hFont = CreateFontIndirectW( &logFont );
    SelectObject( hdc, hFont );
    GCP_RESULTSW infoStr = {0};
    infoStr.lStructSize = sizeof(GCP_RESULTSW);
    wchar_t tempStr[wcslen(sUnicode)];  
    wcscpy( tempStr, sUnicode );
    infoStr.lpGlyphs = tempStr;
    infoStr.nGlyphs = wcslen(tempStr);
    GetCharacterPlacementW( hdc, tempStr, wcslen(tempStr), 0, &infoStr, GCP_GLYPHSHAPE );
    ReleaseDC( hwnd, hdc );

    // Return one string
    if( infoStr.lpGlyphs[0] == 3 || // for Windows 7 and 10
        infoStr.lpGlyphs[0] == 0 )  // for Windows XP
        return sLimited;
    else
        return sUnicode;
}

// Callback function to intercept font creation
int CALLBACK metaFileEnumProc( HDC hdc, HANDLETABLE *table, const ENHMETARECORD *record,
                            int tableEntries, LPARAM logFont ) {
    if( record->iType == EMR_EXTCREATEFONTINDIRECTW ) {
        const EMREXTCREATEFONTINDIRECTW* fontRecord = (const EMREXTCREATEFONTINDIRECTW *)record;
        *(LOGFONTW *)logFont = fontRecord->elfw.elfLogFont;
    }
    return 1;
}

你可以用checkGlyphExist( hWnd, L"????", L"&lt;3" );调用它

我在 Windows 10 和两个虚拟机上进行了测试:Windows 7 Professional、Windows XP SP2。
它工作得很好,但是当基本字体中缺少字形时,EnumEnhMetaFile 检索的后备字体仍然存在两个问题:

  • 在 Windows 7 中始终是 Microsoft Sans Serif,但真正的后备字体应该是 Segoe UI Symbol
  • 在 Windows XP 中是 Tahoma 而不是 Microsoft Sans Serif,但仅适用于代理对字符(对于 BMP 字符是 Microsoft Sans Serif是正确的,我猜)。

有人可以帮我解决这个问题吗?

【问题讨论】:

  • 您可以询问该字体中该字符的宽度
  • 可能是控件在旧系统上使用 Uniscribe,在 Windows 10 中使用 DirectWrite。DirectWrite 支持回到 Vista。
  • @BasileStarynkevitch 你的意思是检查丢失的字形是否为零宽度?我试过GetTextExtentPoint32DrawTextEx(DT_CALCRECT)TextOut+GetPath,如果字形丢失,它们都返回全宽,我想替换框的宽度▯。
  • @eryksun 是的,但我不知道如何拦截菜单按钮的 Uniscribe/DirectWrite 创建以了解是否找到字形:-(
  • 附带说明:通常您会选择一种支持您在应用程序中显示的所有内容的字体,并将该字体与您的应用程序一起提供以避免这些问题,或者使用图像。尽管如此有趣的问题。

标签: c winapi unicode font-linking


【解决方案1】:

首先,您必须确保在 Win7 和 Win10 上使用相同的 API。我认为,较低级别的 gdi32 API 通常不应该支持代理对,而更新的 DirectWrite 在每个级别上都支持。接下来要记住的是字体回退(字体链接是不同的东西)数据因版本而异,它不是用户可以访问的东西,也不能修改。

首先要检查Win7是否在U+1F5A4处为符号提供字体,它可能只是在以后的版本中引入的。

基本上,如果您使用的是系统渲染功能,无论是旧的还是新的,大多数时候您都不应该控制回退,如果它对您不起作用,通常意味着它不起作用。 DirectWrite 允许自定义后备列表,例如,您可以在其中明确地将 U+1F5A4 分配给您想要的任何支持它的字体,包括您可以与应用程序捆绑的自定义字体。

如果您想要更详细的答案,您需要显示一些不适合您的来源摘录。

【讨论】:

  • 赞成票,因为它建议了以下解决方案:首先检查 Windows 版本!然后决定对低版本做什么。
  • 我真的很想找到一个不检查 Windows 版本的解决方案,而是检查系统是否支持代理对。我在我的问题中添加了一个运行良好的与版本无关的代码,等待改进。我用 C 编写代码,而 DirectWrite 只是 C++,我猜。我已经看到不可能只为我的程序菜单设置字体,除非我制作一个所有者绘制的菜单,但在这一点上使用一些图像更容易。
  • @Salvador,对,我不是说你应该直接检查 Windows 版本,它不漂亮。最好检查是否支持功能,但这在您的情况下可能并非易事。您也可以使用 C 语言中的 DirectWrite,这不是为此而设计的,但可以通过一些技巧来实现。
  • 我正在尝试将 DirectWrite 与 C 一起使用,尤其是 GetSystemFontFallback,但对于我的知识贫乏来说,这太难了。
【解决方案2】:

我相信代理对的高位和低位 16 位字已经很好地定义了。您应该能够通过检查每个 16 位字的值范围来识别代理对。

对于高位字,它应该在 0xd800 到 0xdbff 的范围内 对于低位字,它应该在 0xdc00 到 0xdfff 的范围内

如果任何两对“字符”满足此条件,则它们是代理对。

有关更多信息,请参阅有关 UTF-16 的维基百科文章。

【讨论】:

  • 但是 OP 的原始文本已经 一个正确的代理对,并且它正确显示 - 如果字体要求的话。此外,您假设 Font Fallback 例程将更改现有字符串(或可能返回指向字符串的指针)。通常情况并非如此。
  • 只要字体支持相关代码点,GDI 就支持代理对。并且至少从 Win2K 开始,
猜你喜欢
  • 2017-04-06
  • 1970-01-01
  • 2021-04-15
  • 1970-01-01
  • 2012-04-23
  • 1970-01-01
  • 2016-12-21
  • 2016-07-20
  • 2012-08-15
相关资源
最近更新 更多