Как определить, поддерживается ли шрифт символом Unicode суррогатной пары?

Я смог найти следующий метод определения, если Unicode-16 символ поддерживается шрифтом. К сожалению, это не работает для суррогатная пара символов Юникод, поскольку WCRANGE структура поддерживается GetFontUnicodeRanges функция возвращает только WCHAR (16-битные) параметры в качестве вывода.

Вот пример того, что я пытаюсь сделать:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HFONT hFont = NULL;

switch (message)
{
case WM_CREATE:
{
LOGFONT lf = {0};
lf.lfHeight = -64;
::StringCchCopy(lf.lfFaceName, _countof(lf.lfFaceName), L"Arial");

hFont = ::CreateFontIndirect(&lf);
}
break;

case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);

RECT rcClient = {0};
::GetClientRect(hWnd, &rcClient);

HGDIOBJ hOldFont = ::SelectObject(hdc, hFont);

LPCTSTR pStr = L">\U0001F609<";
int nLn = wcslen(pStr);

RECT rc = {20, 20, rcClient.right, rcClient.bottom};
::DrawText(hdc, pStr, nLn, &rc, DT_NOPREFIX | DT_SINGLELINE);

::SelectObject(hdc, hOldFont);

EndPaint(hWnd, &ps);
}
break;

//....

Если я запускаю его на Windows 10, я получаю это:

введите описание изображения здесь

Но вот что я получаю на Windows 7:

введите описание изображения здесь

Так как же определить, не будет ли отображаться этот средний символ?


PS. Я также пытался использовать плохо документирован Uniscribe, и модифицированная версия этот урок В качестве примера. Но что бы я ни делал, он не дал ощутимого результата между Win10 и Win7. Если это поможет ответить на этот вопрос, вот код, с которым я экспериментировал:

//Call from WM_PAINT handler
std::wstring str;
test02(hdc, pStr, str);

RECT rc0 = {0, 200, rcClient.right, rcClient.bottom};
::DrawText(hdc, str.c_str(), str.size(), &rc0, DT_NOPREFIX | DT_SINGLELINE);

а потом:

void test02(HDC hDc, LPCTSTR pStr, std::wstring& str)
{
//'str' = receives debugging outcome (needs to be printed on the screen)

//SOURCE:
//      https://maxradi.us/documents/uniscribe/

HRESULT hr;
SCRIPT_STRING_ANALYSIS ssa = {0};

int nLn = wcslen(pStr);

hr = ::ScriptStringAnalyse(hDc,
pStr,
nLn,
1024,
-1,
SSA_GLYPHS,
0, NULL, NULL, NULL, NULL, NULL, &ssa);

if(SUCCEEDED(hr))
{
const SCRIPT_PROPERTIES **g_ppScriptProperties;
int g_iMaxScript;

hr = ::ScriptGetProperties(&g_ppScriptProperties, &g_iMaxScript);
if(SUCCEEDED(hr))
{
const int cMaxItems = 20;
SCRIPT_ITEM si[cMaxItems + 1];
SCRIPT_ITEM *pItems = si;
int cItems;                 //Receives number of glyphs

SCRIPT_CONTROL scrCtrl = {0};
SCRIPT_STATE scrState = {0};

hr = ::ScriptItemize(pStr, nLn, cMaxItems, &scrCtrl, &scrState, pItems, &cItems);
if(SUCCEEDED(hr))
{
FormatAdd2(str, L"cItems=%d: ", cItems);

int nCntGlyphs = nLn * 4;
WORD* pGlyphs = new WORD[nCntGlyphs];
WORD* pLogClust = new WORD[nLn];
SCRIPT_VISATTR* pSVs = new SCRIPT_VISATTR[nCntGlyphs];

//Go through each run
for(int i = 0; i < cItems; i++)
{
FormatAdd2(str, L"[%d]:", i);

SCRIPT_CACHE sc = NULL;
int nCntGlyphsWrtn = 0;
int iPos = pItems[i].iCharPos;
const WCHAR* pP = &pStr[iPos];
int cChars = i + 1 < cItems ? pItems[i + 1].iCharPos - iPos : nLn - iPos;

hr = ::ScriptShape(hDc, &sc, pP, cChars,
nCntGlyphs, &pItems[i].a, pGlyphs, pLogClust, pSVs, &nCntGlyphsWrtn);

if(SUCCEEDED(hr))
{
std::wstring strGlyphs;
for(int g = 0; g < nCntGlyphsWrtn; g++)
{
FormatAdd2(strGlyphs, L"%02X,", pGlyphs[g]);
}

std::wstring strLogClust;
for(int w = 0; w < cChars; w++)
{
FormatAdd2(strLogClust, L"%02X,", pLogClust[w]);
}

std::wstring strSVs;
for(int g = 0; g < nCntGlyphsWrtn; g++)
{
FormatAdd2(strSVs, L"%02X,", pSVs[g]);
}

FormatAdd2(str, L"c=%d {G:%s LC:%s SV:%s} ", nCntGlyphsWrtn, strGlyphs.c_str(), strLogClust.c_str(), strSVs.c_str());int* pAdvances = new int[nCntGlyphsWrtn];
GOFFSET* pOffsets = new GOFFSET[nCntGlyphsWrtn];
ABC abc = {0};

hr = ::ScriptPlace(hDc, &sc, pGlyphs, nCntGlyphsWrtn, pSVs, &pItems[i].a, pAdvances, pOffsets, &abc);
if(SUCCEEDED(hr))
{
std::wstring strAdvs;
for(int g = 0; g < nCntGlyphsWrtn; g++)
{
FormatAdd2(strAdvs, L"%02X,", pAdvances[g]);
}

std::wstring strOffs;
for(int g = 0; g < nCntGlyphsWrtn; g++)
{
FormatAdd2(strOffs, L"u=%02X v=%02X,", pOffsets[g].du, pOffsets[g].dv);
}FormatAdd2(str, L"{a=%d,b=%d,c=%d} {A:%s OF:%s}", abc.abcA, abc.abcB, abc.abcC, strAdvs.c_str(), strOffs.c_str());
}delete[] pAdvances;
delete[] pOffsets;

}//Clear cache
hr = ::ScriptFreeCache(&sc);
assert(SUCCEEDED(hr));
}

delete[] pSVs;
delete[] pGlyphs;
delete[] pLogClust;
}
}

hr = ::ScriptStringFree(&ssa);
assert(SUCCEEDED(hr));
}
}

std::wstring& FormatAdd2(std::wstring& str, LPCTSTR pszFormat, ...)
{
va_list argList;
va_start(argList, pszFormat);
int nSz = _vsctprintf(pszFormat, argList) + 1;

TCHAR* pBuff = new TCHAR[nSz];      //One char for last null
pBuff[0] = 0;
_vstprintf_s(pBuff, nSz, pszFormat, argList);
pBuff[nSz - 1] = 0;

str.append(pBuff);

delete[] pBuff;

va_end(argList);

return str;
}

6

Решение

�� символ фактически не поддерживается в шрифте Windows 10 Arial. Windows 10 использует "Segoe UI Emoji" в качестве резервного шрифта для этой конкретной кодовой точки.

Итак, сначала мы должны выяснить, используется ли резервный шрифт. Затем проверьте индекс глифа, чтобы увидеть, является ли он символом тофу (обычно отображается в виде квадратного знака )

Мы можем использовать метафайл, чтобы найти, используется ли замена шрифта. Выберите этот шрифт в HDC,

использование ScriptGetFontProperties чтобы найти значения для неподдерживаемых глифов.

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

#include <iostream>
#include <windows.h>
#include <usp10.h>

int CALLBACK metafileproc(HDC, HANDLETABLE*, const ENHMETARECORD *record,
int, LPARAM logfont)
{
if(record->iType == EMR_EXTCREATEFONTINDIRECTW)
{
auto ptr = (const EMREXTCREATEFONTINDIRECTW*)record;
*(LOGFONT*)logfont = ptr->elfw.elfLogFont;
}
return 1;
}

HFONT GetFallbackFont(const wchar_t *str, HFONT hfont_test)
{
//use metafile to find the fallback font
auto metafile_hdc = CreateEnhMetaFile(NULL, NULL, NULL, NULL);
auto metafile_oldfont = SelectObject(metafile_hdc, hfont_test);

SCRIPT_STRING_ANALYSIS ssa;
ScriptStringAnalyse(metafile_hdc, str, wcslen(str), 0, -1,
SSA_METAFILE | SSA_FALLBACK | SSA_GLYPHS | SSA_LINK,
0, NULL, NULL, NULL, NULL, NULL, &ssa);
ScriptStringOut(ssa, 0, 0, 0, NULL, 0, 0, FALSE);
ScriptStringFree(&ssa);

SelectObject(metafile_hdc, metafile_oldfont);
auto hmetafile = CloseEnhMetaFile(metafile_hdc);

LOGFONT logfont = { 0 };
EnumEnhMetaFile(0, hmetafile, metafileproc, &logfont, NULL);
wprintf(L"Selecting fallback font: %s\n", logfont.lfFaceName);
HFONT hfont = CreateFontIndirect(&logfont);
DeleteEnhMetaFile(hmetafile);

return hfont;
}

bool IsTofu(HDC hdc, HFONT hfont_test, const wchar_t *str)
{
if(wcsstr(str, L" "))
{
wprintf(L"*** cannot test strings containing blank space\n");
}

auto hfont = GetFallbackFont(str, hfont_test);
auto oldfont = SelectObject(hdc, hfont);

//find the characters not supported in this font
//note, blank space is blank, unsupported fonts can be blank also
SCRIPT_CACHE sc = NULL;
SCRIPT_FONTPROPERTIES fp = { sizeof(fp) };
ScriptGetFontProperties(hdc, &sc, &fp);
ScriptFreeCache(&sc);
wprintf(L"SCRIPT_FONTPROPERTIES:\n");
wprintf(L" Blank: %d, Default: %d, Invalid: %d\n",
fp.wgBlank, fp.wgDefault, fp.wgInvalid);

// Get glyph indices for the string
GCP_RESULTS gcp_results = { sizeof(GCP_RESULTS) };
gcp_results.nGlyphs = wcslen(str);
auto wstr_memory = (wchar_t*)calloc(wcslen(str) + 1, sizeof(wchar_t));
gcp_results.lpGlyphs = wstr_memory;
GetCharacterPlacement(hdc, str, wcslen(str), 0, &gcp_results, GCP_GLYPHSHAPE);

//check the characters against wgBlank...
bool istofu = false;
wprintf(L"Glyphs:");
for(UINT i = 0; i < gcp_results.nGlyphs; i++)
{
wchar_t n = gcp_results.lpGlyphs[i];
wprintf(L"%d,", (int)n);
if(n == fp.wgBlank || n == fp.wgInvalid || n == fp.wgDefault)
istofu = true;
}
wprintf(L"\n");

free(wstr_memory);
SelectObject(hdc, oldfont);
DeleteObject(hfont);

return istofu;
}

int main()
{
const wchar_t *str = L"123��456";

auto hdc = GetDC(0);
auto memdc = CreateCompatibleDC(hdc);
auto hbitmap = CreateCompatibleBitmap(hdc, 1, 1);
auto oldbmp = SelectObject(memdc, hbitmap);
auto hfont = CreateFont(10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Segoe UI");

printf("IsTofu: %d\n", IsTofu(hdc, hfont, str));

SelectObject(memdc, oldbmp);
DeleteObject(hbitmap);
DeleteDC(memdc);
ReleaseDC(0, hdc);
DeleteObject(hfont);

return 0;
}

Выход:

IsTofu является false для Windows 10.

Это будет true для некоторых старых версий Windows. Но это не проверено в WinXP


С помощью GetUniscribeFallbackFont от эта ссылка


Обратите внимание, документация Windows описывает GetCharacterPlacement как устаревший, он рекомендует использовать функции Uniscribe. Но я не знаю, какую замену использовать для этого здесь.

4

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]