Getting all of the localized names of a font

by Michael S. Kaplan, published on 2006/02/13 06:31 -05:00, original URI: http://blogs.msdn.com/b/michkap/archive/2006/02/13/530814.aspx


Greg Tavares asked in the Suggestion Box:

I am desparately trying to figure out how to get both the English names of fonts AND the localized names through the Win32 API.

I'm on Japanese Windows XP.  I call EnumFontFamiliesEx.  By default I get the localized font names.  I also want the English names.  I tried calling SetThreadLocale() as well as _wsetlocale() before calling EnumFontFamiliesEx but neither seemed to have any effect.

Could you please pass me a clue?  I'd be forever in your debt.

Keep in mind that I did mention previously in East Asian Font Names that both the English names and the localized ones would work (keeping in mind the limitations I pointed out in Ready... Set... Reboot Redux, of course!).

But there really is no Win32 API function to retrieve all of those names, unfortunately. Which means you are a little stuck here, unless you dig into the OpenType Specification, of course....

Now I have never really done very much with the OpenType format (with the exception of a port of the code in MS Knowledge Base article 241358 for MSLU's GetGlyphOutlineW wrapper), so I decided to dig in and see how much work it would be. Here follows the code I put together, it should qualify as a very strong clue (though of course you won't be in my debt, Greg. This was fun!).

If there are people who know more about fonts out there (just about anyone in typography, I would imagine!) who want to point out my most blatant errors, I would be happy. :-)

(Disclaimer: I looked at no internal information and no external samples to write this, and I did it all in a few hours when I probably should have been sleeping; you could almost certainly do better from alternate sources of information!)

#define _UNICODE
#define UNICODE
#include <stdio.h>
#include <windows.h>

struct NAMERECORD {
  USHORT platformID;
  USHORT encodingID;
  USHORT languageID;
  USHORT nameID;
  USHORT length;
  USHORT offset;
};

#define SWAPBYTES(w)  ( ((USHORT)((w)[0]) << 8) | (w)[1])

void main() {
  CHOOSEFONT cf = {0};
  LOGFONT lf;
  HFONT hfont;
 
  cf.lStructSize = sizeof(CHOOSEFONT);
  cf.lpLogFont = &lf;
  cf.Flags = CF_SCREENFONTS | CF_TTONLY | CF_NOSCRIPTSEL; 
  cf.nFontType = SCREEN_FONTTYPE;
 
  ChooseFont(&cf);
 
  hfont = CreateFontIndirect(cf.lpLogFont);
  if(hfont) {
    HDC hdc = ::CreateDC(L"Display", NULL, NULL, NULL);
    if(hdc) {
      HFONT hfontOld = (HFONT)::SelectObject(hdc, hfont);
      if(hfontOld) {
        DWORD dw;
       
        dw = ::GetFontData(hdc, 'eman', 0, NULL, 0);
        if(dw != GDI_ERROR) {
          LPBYTE lpv = (BYTE *)malloc(dw);
          UINT offset = 0;
          if(lpv) {
            dw = ::GetFontData(hdc, 'eman', 0, lpv, dw);
            if(dw != GDI_ERROR) {
              USHORT format, count, stringOffset;
              NAMERECORD * rgNR;

              format = SWAPBYTES(&lpv[offset]);
              offset += sizeof(USHORT);
              count = SWAPBYTES(&lpv[offset]);
              offset += sizeof(USHORT);
              stringOffset = SWAPBYTES(&lpv[offset]);
              offset += sizeof(USHORT);

              rgNR = (struct NAMERECORD *)calloc(count, sizeof(NAMERECORD));

              for(int i = 0; i < count; i++) {
                rgNR[i].platformID = SWAPBYTES(&lpv[offset]);
                offset += sizeof(USHORT);
                rgNR[i].encodingID = SWAPBYTES(&lpv[offset]);
                offset += sizeof(USHORT);
                rgNR[i].languageID = SWAPBYTES(&lpv[offset]);
                offset += sizeof(USHORT);
                rgNR[i].nameID   = SWAPBYTES(&lpv[offset]);
                offset += sizeof(USHORT);
                rgNR[i].length   = SWAPBYTES(&lpv[offset]);
                offset += sizeof(USHORT);
                rgNR[i].offset   = SWAPBYTES(&lpv[offset]);
                offset += sizeof(USHORT);

                // The name string is at
                // lpv + stringOffset + rgNR[i].offset
                // The name string length is rgNR[i].length

                // The bytes in both cases must each be run
                // through something like the SWAPBYTES macro
                // because all font information is big endian
                // regardless of endian-ness of the platform).

                //
                // INSERT CODE TO DO SOMETHING WITH NAMES HERE
                // 
              }
              free(rgNR);
            }
            free(lpv);
          }
        }
        ::SelectObject(hdc, hfontOld);
      }
      ::DeleteDC(hdc);
    }
    ::DeleteObject(hfont);
  }
}

For meanings of the platform IDs, encoding IDs, language IDs, and name IDs, see The OpenType Specification's page about the Naming Table.

The localized version(s) of these strings can be found by looking for the appropriate language ID.

I was not tempted to write an EnumFontFamilesExEx, at least not enough to actually write it. But by picking the right name ID etc. it should be possible for the very ambitious.... :-)

 

This post brought to you by "" (U+30b4, a.k.a. KATAKANA LETTER GO)
(The first letter in ゴシック, a.k.a. Gothic, used by the MS Gothic (MS ゴシック) font)


Joe on 29 May 2009 2:58 AM:

How can we get the PostScript name of a font, if we have only the Windows menu name of the font? Is there any way to  get it using win32 API?


referenced by

2011/09/13 "It was an honest mistake -- I'm not running NT4; I'm just in Hong Kong!"

2008/07/11 On installing and removing fonts, Part 4: The easiest part is the addition!

2008/07/03 On installing and removing fonts, Part 1: Do I know you, or some version of you at least?

2008/07/01 Is that character in the font or isn't it?

2007/09/24 Documented, schmockumented! It's still kind of cool....

2007/07/07 TTC indexes, the easy way...

2007/06/22 TTC indexes, the hard way...

2007/03/26 Hello, Mr. TrueType Font. Do you support Bengali?

2007/01/22 Getting all of the localized names of a font[.NET]

2006/10/21 Is Font.FontFamily localized?

go to newer or older post, or back to index or month or day