Проверьте, отображается ли символ Unicode или тофу

Мой вопрос похож на этот, но немного шаг вперед.

В моей программе Win32 у меня есть несколько кнопок меню с символами Unicode над BMP, такими как U+1F5A4 (суррогатные пары UTF-16 0xD83D 0xDDA4).
В Windows 10 системный шрифт Segoe UI не имеет этого глифа: он автоматически заменяется глифом из шрифта Segoe UI Symbol и правильно отображается в кнопке благодаря процессу, называемому связыванием шрифтов (или откатом шрифта, все еще неясным мне).
Но в Windows 7 связывание шрифтов приводит к шрифту, который также не имеет этого символа, и суррогатные пары отображаются в виде двух пустых полей ▯▯. То же самое в Windows XP со шрифтом Tahoma.

Я хочу избежать этих заменяющих блоков, анализируя текст до или после присвоения кнопке, и заменяя отсутствующий глиф некоторым общим символом ASCII.

Я старался GetGlyphOutline, ScriptGetCMap, GetFontUnicodeRanges а также GetGlyphIndices но они не поддерживают суррогатные пары.
Я тоже пробовал GetCharacterPlacement и отписать ScriptItemize+ScriptShape которые поддерживают суррогатные пары, но все эти функции выполняют поиск только в базовом шрифте HDC (Segoe UI), они не ищут в конечном итоге запасной шрифт (Segoe UI Symbol), который предоставляет глиф.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink это место, где я искал, но я действительно думаю, что это не то, что система использует шрифты для ссылки.

Вопрос в том, как я могу узнать, дает ли системное связывание шрифтов правильные блоки глифа или тофу?


редактировать

Я нашел какое-то решение, копирующее этот код и добавляющее последний 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"<3" );

Я тестировал на Windows 10 и на двух виртуальных машинах: Windows 7 Professional, Windows XP SP2.
Он работает довольно хорошо, но по-прежнему остаются две проблемы с резервным шрифтом, который EnumEnhMetaFile получает, когда глиф отсутствует в базовом шрифте:

  • в Windows 7 всегда используется Microsoft Sans Serif, но настоящим резервным шрифтом должен быть Segoe UI Symbol.
  • в Windows XP вместо Microsoft Sans Serif используется Tahoma, но только для символов суррогатных пар (для символов BMP - правильный Microsoft Sans Serif, я полагаю).

Может кто-нибудь помочь мне решить это?

2 ответа

Сначала вы должны убедиться, что вы используете один и тот же API на Win7 и Win10. Я думаю, что API gdi32 более низкого уровня не должен поддерживать суррогатные пары в целом, в то время как более новый DirectWrite поддерживает на каждом уровне. Следующее, что нужно иметь в виду, это то, что резервный шрифт (связывание шрифтов - это другое) данные отличаются от выпуска к выпуску, и это не то, к чему у пользователя есть доступ, и они не могут быть изменены.

Второе, что нужно проверить, если Win7 предоставляет шрифты для символа в U+1F5A4, возможно, он был представлен только в более поздних версиях.

В основном, если вы используете функциональность системного рендеринга, более старого или более нового, вы не должны управлять резервным режимом большую часть времени, если он не работает для вас, это обычно означает, что он не будет работать. DirectWrite позволяет создавать пользовательские резервные списки, где вы можете, например, явно назначить U+1F5A4 любому шрифту, который вам нужен, который поддерживает его, включая пользовательские шрифты, которые вы можете связать с вашим приложением.

Если вы хотите получить более подробный ответ, вам нужно показать некоторые выдержки из источников, которые вам не подходят.

Я считаю, что верхние и нижние 16-битные слова хорошо определены для суррогатных пар. Вы должны быть в состоянии идентифицировать суррогатные пары, проверив диапазон значений для каждого из 16-битных слов.

Для старшего слова оно должно находиться в диапазоне от 0xd800 до 0xdbff. Для младшего слова оно должно находиться в диапазоне от 0xdc00 до 0xdfff.

Если любые две пары "символов" соответствуют этим критериям, они являются суррогатной парой.

См. Статью в Википедии о UTF-16 для получения дополнительной информации.

Другие вопросы по тегам