I was able to find the following method of determining if a Unicode-16
character is supported by a font. Unfortunately that doesn't work for surrogate pair Unicode characters, since WCRANGE
struct supported by GetFontUnicodeRanges
function returns only WCHAR
(16-bit) parameters as output.
Here's an example of what I'm trying to do:
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;
//....
If I run it on Windows 10, I get this:
But this is what I'm getting on Windows 7:
So what is the way to tell if that middle character is not going to be rendered?
PS. I also attempted to use badly documented Uniscribe, and a modified version of this tutorial as an example. But no matter what I did, it failed to produce a discernible outcome between Win10 and Win7. If it helps to answer this question, here's the code I experimented with:
//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);
and then:
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;
}
EDIT: I was able to create a demo GUI app that demonstrates the solution suggested by Barmak Shemirani below.
character is actually not supported in Windows 10 Arial font. Windows 10 uses
"Segoe UI Emoji"
as fallback font for that particular code point.So first we have to figure out if fallback font is used. Then check the glyph index to see if it is tofu character (usually shown as square sign
▯
)We can use meta file to find if font substitution is used. Select that font in to
HDC
.Use
ScriptGetFontProperties
to find the values for unsupported glyphs.Use
GetCharacterPlacement
to find the glyph indices for the string. If the glyph index matches unsupported glyphs, then the code point is being printed as tofu▯
.Edit:
If you try to print Chinese character etc. then you have to choose the appropriate font (SimSun for Chinese)
This part is done by
IMLangFontLink
. It's a different type of font substitution. The example below will test for single code point (it can be expanded to handle a string).If
Segoe UI
font is selected, then for the Chinese character请
, it will switchSegoe UI
toSimSun
.For emojis, it will switch
Segoe UI
toSegoe UI Emoji
See also this article in oldnewthing. Note the article in OldNewThing does not handle Emojis, it just lets
TextOut
handle it (which is handled correctly in Windows 10 so the result appears okay)Output:
IsTofu
isfalse
for Windows 10.It will be
true
for some older Windows versions. But this is not tested in WinXPUsing
GetUniscribeFallbackFont
from this linkNote, Windows documentation describes
GetCharacterPlacement
as obsolete, it recommends using Uniscribe functions. But I don't know what replacement to use for it here.