Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Non-Unicode apps system locale makes Unicode fonts with symbol charset displayed incorrectly?

I'm trying to display Unicode chars from Wingdings font (it's Unicode TrueType font supporting symbol charset only). It's displayed correctly on my Win7/64 system using corresponding regional OS settings:

  • Formats: Russian
  • Location: Russia
  • System locale (AKA Language for Non-Unicode applications): English

But if I switch System locale to Russian, Unicode characters with codes > 127 are displayed incorrectly (replaced with boxes).

My application is created as using Unicode Charset in Visual Studio, it calls only Unicode Windows API functions.

Also I noted that several Windows apps also display such chars incorrectly with symbol fonts (Symbol, Wingdings, Webdings etc), e.g. Notepad, Beyond Compare 3. But WordPad and MS Office apps aren't affected.

Here is minimal code snippet (resources cleanup skipped for brevity):

LOGFONTW lf = { 0 };
lf.lfCharSet = SYMBOL_CHARSET;
lf.lfHeight = 50;
wcscpy_s(lf.lfFaceName, L"Wingdings");

HFONT f = CreateFontIndirectW(&lf);

SelectObject(hdc, f);

// First two chars displayed OK, 3rd and 4th aren't (replaced with boxes) if
// Non-Unicode apps language is NOT English.
TextOutW(hdc, 10, 10, L"\x7d\x7e\x81\xfc");

So the question is: why the hell Non-Unicode apps language setting affects Unicode apps?

And what is the correct (and most simple) way to display SYMBOL_CHARSET fonts without dependency to OS system locale?

like image 744
Rost Avatar asked Jan 12 '23 08:01

Rost


1 Answers

The root cause of the problem is that Wingdings font is actually non-Unicode font. It supports Unicode partially, so some symbols are still displayed correctly. See @Adrian McCarthy's answer for details about how it's probably works under the hood.

Also see more info here: http://www.fileformat.info/info/unicode/font/wingdings and here: http://www.alanwood.net/demos/wingdings.html

So what can we do to avoid such problems? I found several ways:

1. Quick & dirty

Fall back to ANSI version of API, as @user1793036 suggested:

TextOutA(hdc, 10, 10, "\x7d\x7e\x81\xfc"); // Displayed correctly!

2. Quick & clean

Use special Unicode range F0 (Private Use Area) instead of ASCII character codes. It's supported by Wingdings:

TextOutW(hdc, 10, 10, L"\xf07d\xf07e\xf081\xf0fc"); // Displayed correctly!

To explore which Unicode symbols are actually supported by font some font viewer can be used, e.g. dp4 Font Viewer

3. Slow & clean, but generic

But what to do if you don't know which characters you have to display and which font actually will be used? Here is most universal solution - draw text by glyphs to avoid any undesired translations:

void TextOutByGlyphs(HDC hdc, int x, int y, const CStringW& text)
{
   CStringW glyphs;

   GCP_RESULTSW gcpRes = {0};
   gcpRes.lStructSize = sizeof(GCP_RESULTS);
   gcpRes.lpGlyphs = glyphs.GetBuffer(text.GetLength());
   gcpRes.nGlyphs = text.GetLength();

   const DWORD flags = GetFontLanguageInfo(hdc) & FLI_MASK;
   GetCharacterPlacementW(hdc, text.GetString(), text.GetLength(), 0,
     &gcpRes, flags);
   glyphs.ReleaseBuffer(gcpRes.nGlyphs);

   ExtTextOutW(hdc, x, y, ETO_GLYPH_INDEX, NULL, glyphs.GetString(),
      glyphs.GetLength(), NULL);
}

TextOutByGlyphs(hdc, 10, 10, L"\x7d\x7e\x81\xfc"); // Displayed correctly!

Note GetCharacterPlacementW() function usage. For some unknown reason similar function GetGlyphIndicesW() would not work returning 'unsupported' dummy values for chars > 127.

like image 168
Rost Avatar answered Jan 29 '23 07:01

Rost