28

I'm trying to create a list of fonts for the user to choose from. I'm doing this by using the EnumFontFamiliesEx function but unfortunately, the list of returned fonts is much too long. There are many extra fonts that seem frivolous, duplicate, for a different language, or otherwise undesirable to display to the user. My screenshot best illustrates the junk I am trying to filter out.

My code for calling EnumFontFamiliesEx looks like this:

LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfCharSet = DEFAULT_CHARSET;
// screenDC is result of CreateCompatibleDC(NULL)
EnumFontFamiliesEx(screenDC, &lf, GetFontsCallback, NULL, 0);

The resulting list looks like this, after sorting alphabetically and removing fonts with duplicate face names:

enter image description here

As you can see, the ChooseFont font common dialog is displaying a very reasonable list of fonts that is user-friendly and makes sense. On the other hand, my code displays a long list of extra fonts: fonts that start with "@" (why? what are they even for?), 3 extra variants of Arial font, and several other fonts of unknown purpose like Aheroni, Andalus, Angsana New, AngsanaUPC, and so on. It's insane.

How do I filter the list of fonts returned by EnumFontFamiliesEx so that it exactly matches the list shown in the ChooseFont dialog?

James Johnston
  • 9,264
  • 9
  • 48
  • 76
  • The docs are pretty clear that a couple of the arguments will change the number/type of fonts returned. You are passing in defaults. Have you tried being more specific? – Ed S. Jun 28 '12 at 22:39
  • What about just adding a search and or filters in your user interface? – Eric des Courtis Jun 28 '12 at 22:40
  • This is pretty heavy culture bias. Your East Asian user will find something worth while to pick in that dialog. Like all the ones with a @ in front of it. Fonts that support vertical text. You can certainly fix that problem, uninstall the fonts you don't every care about. – Hans Passant Jun 28 '12 at 22:41
  • @Ed S: The only way I can see to filter via argument is to preset `lfCharSet` and/or `lfPitchAndFamily`. However, for `lfPitchAndFamily` it says "Must be set to zero for all language versions of the operating system" and most of the offending fonts after running my example have the returned `lfCharSet` value to be an ANSI charset. I also used an API monitor to see what the `ChooseFont` dialog does; it does call `EnumFontFamiliesEx` but with DEFAULT_CHARSET; using its parameters does not thin my list much. So apparently `ChooseFont` does its own filtering but I have no idea what. – James Johnston Jun 28 '12 at 22:45
  • @JamesJohnston: Ah, ok then (you may want to add that to your question). It must filter on its own then, not sure what logic it uses to separate them out though. – Ed S. Jun 28 '12 at 22:49
  • @JamesJohnston : Probably on `lfCharSet` of the `LOGFONT`s received (note the 'Script' dropdown in your screenshot). – ildjarn Jun 28 '12 at 22:52
  • 3
    @HansPassant: Another way to look at it is that a western user does not care to look at East Asian fonts. There are 295 fonts in this list. I count 148 fonts in the Notepad `ChooseFont` list. Somehow the `ChooseFont` dialog got smart and removed a bunch of fonts. For consistency sake, I want my app to match this dialog. Of course, if Windows 7 is used in East Asia, I'd expect it to focus on East Asian fonts & I'd want my app to do the same. If you think this is "pretty heavy culture bias" - Microsoft is doing the same in Windows 7 WordPad & Paint, MS Word 2010, `ChooseFont` dialog, etc. – James Johnston Jun 28 '12 at 22:52
  • 1
    @ildjarn: The `Script` dropdown probably corresponds to the `elfScript` member of `ENUMLOGFONTEX` structure ( http://msdn.microsoft.com/en-us/library/dd162627(VS.85).aspx ) - I've already examined this and most offending fonts have "Western" script. (Even if some filtration on this is necessary, it's not clear where I should get the string to compare to from, since I'd want to be aware of the user's current culture). – James Johnston Jun 28 '12 at 22:59
  • Also, could you show `GetFontsCallback`? Someone might be able to improve filtering if you show the code. – Jesse Good Jun 28 '12 at 23:30
  • 5
    After some searching, I found [this website](http://vb.mvps.org/samples/FontFilter/) talking about the issue, it's using VB, but you might be able to get some help from it. – Jesse Good Jun 28 '12 at 23:49
  • 1
    "Another way to look at it is that a western user does not care to look at East Asian fonts" -- Not quite. A number of posters are westerners asking for help because they DO care about displaying their customers' languages correctly. – Windows programmer Jun 29 '12 at 05:39
  • 1
    "Like all the ones with a @ in front of it. Fonts that support vertical text." -- True but those less frequently than the ones without @. Unless you're producing a mass market magazine (i.e. not a technical journal), newspaper, advertisement, etc., you'll write horizontally. It would be friendlier to put those @ fonts at the end of the list instead of the beginning. – Windows programmer Jun 29 '12 at 05:41

4 Answers4

29

Thanks to Jesse Good, I have now learned about some insane unfortunate design decisions made by the Windows 7 team. I won't accept my own answer yet, because if someone else comes up with a way of using this hidden font feature in Windows 7 even when the registry key doesn't exist yet (e.g. perhaps by using the undocumented API, or some other trickery) and their answer works, I will accept it.

This filtering is done by actually "hiding" fonts in Windows 7 Control Panel. By default, fonts for other locales are hidden but they can be shown by the user. At least, this is the idea. Here is the MSDN page discussing this feature: International Font Management.

Here are some key excerpts from this page and other nearby pages in MSDN (also see http://msdn.microsoft.com/en-us/library/windows/desktop/dd371704(v=vs.85).aspx from the Windows 7 compatibility cookbook):

Starting with Windows 7, the font management infrastructure supports the hiding of fonts which are not appropriate for a user's font selection lists. ... This feature means users need no longer be faced with long lists of inappropriate fonts.

In Windows 7, there are no APIs for directly querying which fonts are hidden, or for setting fonts to be hidden. [emphasis mine] If you use the Windows ChooseFont API (Font common dialog) to enable font selection today, you will get the new behavior for free. The new Windows Scenic Ribbon (Font Controls) introduced in Windows 7 also supports this behavior and provides another reason to "Ribbonize" your applications.

When a font is selected into a device context, there is no effect on drawing due to the font being hidden. The EnumFontFamiliesEx function continues to enumerate fonts that are set to hidden. [emphasis mine; there is apparently no way to differentiate hidden and visible fonts with EnumFontFamiliesEx]

Note that charsets are a legacy notion corresponding to pre-Unicode character sets. [emphasis mine]

ChooseFont will only list the shown fonts and filter out the hidden fonts while displaying fonts in the list box. The additional flag (CF_INACTIVEFONTS) in the flags member of the CHOOSEFONT structure is added to allow you to display all the installed fonts in the font list, the same as ChooseFont behaved before Windows 7.

So in other words, unless you use the ChooseFont common dialog or the official Windows ribbon control (only available on Windows Vista/7), you have no supported way at all of filtering out hidden fonts. Is it any surprise or wonder that many users on the Internet are complaining that hiding fonts in the Windows 7 Control Panel seems to have no effect?!? (I previously false posted that MS Word 2010 filters out hidden fonts. It appears it does not, because they use their own custom ribbon control and not the ribbon built into Windows. It is amusing that the Windows 7 Font Control Panel, by design, is not compatible with one of Microsoft's flagship products and cannot be made compatible without dumping the more powerful ribbon in Office.)

Based on the the link that Jesse Good posted, I learned that the hidden fonts are stored in an undocumented registry key. Through this link, and also some experimentation and analysis with Process Monitor (looking at both stack traces and registry accesses), I learned the following:

  • The ribbon control calls an undocumented function called FmsGetFilteredFontList in FMS.DLL (Font Management Services). Its purpose appears quite obvious. It's a real shame they couldn't be bothered to publicly document and maintain it.
  • The settings are stored in an undocumented registry key, which is accessed by FMS.DLL.
  • If the registry key is deleted, it is recreated with default settings by FmsGetFilteredFontList, which are to hide fonts that are not related to the current input languages.
  • A brand new user profile created on a clean installation of Windows does NOT contain any registry keys related to what fonts should be hidden.

Therefore, the link posted by Jesse Good might work for many/most cases, but not 100% of the time. You need a way to reliably recreate these registry keys (or at least assume defaults) if they don't exist. The default behavior is still to hide some fonts, even if the registry keys are gone (e.g. on a new user profile).

James Johnston
  • 9,264
  • 9
  • 48
  • 76
5

Given that FmsGetFilteredFontList is undocumented, your options for getting exactly the same list that a user sees in a Windows 7+ ChooseFont dialog may be limited. It may, however, be possible to get a good approximation to the default font list by using only documented APIs.

I've done something similar in order to cut down on the number of possibilities for an algorithm that automatically chooses an appropriate font.

My approach was to use the Unicode subrange bitmask in the FONTSIGNATURE, which can be inspected as you enumerate the fonts. If you know which Unicode subrange(s) you need, the font signature will tell you if the current font covers it. If it does, include it in the list. If it doesn't, then skip it. I suspect this is probably similar to how FmsGetFilteredFontList builds its default list.

The trick is to figure out what subrange(s) the user needs. In my case, it was relatively easy, because I knew exactly what text I was going to have to render. I built a mapping of subranges to FONTSIGNATURE-style bitmask values based on the documentation.

I scanned the code points in the text-to-be-rendered, looking them up in the mapping, and built up a target bitmask. I bitwise-anded this target bitmask with the one in the font signature for each enumerated font. Whenever the result matched the target bitmask, I knew the font could (most likely) support the text. For my application, I required all of the target bits to be present in the font. For your application, I think you'd want any of the target bits.

The font signature bitmask is a good first cut at what characters the font provides. You can use GetFontUnicodeRanges to be utterly certain, but I found that wasn't necessary and it was also slower than just checking the font signatures.

In your case, perhaps you'd have some representative text string available in the user's language. For example, from the document they are editing or from a UI resource that's been translated. You could scan that sample text to get the target font signature.

For example, if you scan some English text, you'll find that all of the necessary characters are in the Latin subrange. If you look at the fonts control panel applet in Windows 7 for an English user (and switch to the details view), you'll see that the Show/hide column is strongly correlated with whether Latin is listed in the Designed for column, which appears to be a textual representation of the font signature's Unicode subrange bitmask.

Update: I just tried enumerating fonts using DirectWrite, thinking that this newer API might handle the font-hiding feature. Alas, it returns everything and has not options (that I can find) for filtering out hidden fonts.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
2

It's disgraceful that Microsoft haven't documented this functionality, to be honest, but more and more this is what we've come to expect from them.

Another way to filter your own list of fonts is to leverage the shell by enumerating the fonts folder. If you look with Explorer you'll see that hidden fonts are displayed with a ghosted icon - we can use that attribute to tell if the font is hidden.

For example (not complete):

LPITEMIDLIST pidlFonts;
if (SUCCEEDED(SHGetKnownFolderIDList(FOLDERID_Fonts, 0, nullptr, &pidlFonts)))
{
    CComPtr<IShellFolder> psf;
    if (SUCCEEDED(SHBindToObject(nullptr, pidlFonts, nullptr, IID_IShellFolder, reinterpret_cast<void**>(&psf))))
    {
        CComPtr<IEnumIDList> pEnum;
        if (SUCCEEDED(psf->EnumObjects(hWnd, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN | SHCONTF_INIT_ON_FIRST_NEXT, &pEnum)))
        {
            LPITEMIDLIST pidl;
            ULONG celt = 0;
            while (pEnum->Next(1, &pidl, &celt) == S_OK)
            {
                SFGAOF hidden = SFGAO_GHOSTED;
                if (SUCCEEDED(psf->GetAttributesOf(1, const_cast<LPCITEMIDLIST*>(&pidl), &hidden)) && (hidden & SFGAO_GHOSTED) == SFGAO_GHOSTED)
                {
                    // this font should be hidden
                    // get its name via IShellFolder::GetDisplayNameOf
                }
                CoTaskMemFree(pidl);
            }
        }
    }
    CoTaskMemFree(pidlFonts);
}

You could use this method to build a set of hidden fonts and then use that to filter the results of EnumFontFamiliesEx.

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79
-2

I think the entire discussion here is misleading.

When I offer a font selector to my users, why should I care what fonts are hidden by Microsoft? And why should I hide all fonts that Microsoft thinks should be hidden by default?

What if my user wants to use just one of those fonts that Microsoft has hidden? Would I put the burden on my user to go to control panel to un-hide this font?

What if some day a chinese user wants to write a chinese text on an english Windows and the chinese font is hidden?

I think there is a much better way to restrict the large amount of fonts returned by EnumFontFamiliesEx().

I wrote my own font selector that has a font filter which allows the user to select the group of fonts he wants to use. This way I do not hide anything and give all the power to the user rather than to Microsoft!

The user might WANT to see ALL fonts! Sometimes one just needs Arial Black or Arial Narrow although Microsoft considers it should be hidden.

int CALLBACK EnumFontFamExProc(const LOGFONT* pk_Font, 
                               const TEXTMETRIC* pk_Metric, 
                               DWORD e_FontType, 
                               LPARAM lParam)
{
    if (e_FontType & TRUETYPE_FONTTYPE)
    {   
        // u32_Flags128 = DWORD[4] = 4 * 32 bit = 128 bit
        DWORD* u32_Flags128 = ((NEWTEXTMETRICEX*)pk_Metric)->ntmFontSig.fsUsb;

        if (u32_Flags128[13 / 32] & (1 << (13 % 32)))
        {
            // the font contains arabic characters (bit 13)
        }
        if (u32_Flags128[38 / 32] & (1 << (38 % 32)))
        {
            // the font contains mathematical symbols (bit 38)
        }
        if (u32_Flags128[70 / 32] & (1 << (70 % 32)))
        {
            // the font contains tibetan characters (bit 70)
        }
    }

In the callback you get a 128 Bit flag that defines exactly which Unicode areas are supported by the font.

See http://msdn.microsoft.com/en-us/library/dd374090%28v=vs.85%29.aspx

You can use these 128 Bits to filter and reduce the count of fonts that you show in the font list:

enter image description here

Elmue
  • 7,602
  • 3
  • 47
  • 57
  • 4
    You've missed the point. The fonts are not "hidden by Microsoft". They are hidden by the user. Microsoft hide some by default, but the user has ultimate control. – Jonathan Potter Jul 27 '14 at 08:09
  • No. I understood perfectly. I do not want to put the burden on my users that they have to go to control panel to enable Arial Black which is hidden by Microsoft's defaults. I want to allow the user to chose from ANY of the fonts he has installed without making it more complicated than necessary. – Elmue Jul 28 '14 at 19:48
  • I added some code. You understand now? You must go to the MSDN link above to understand the values. – Elmue Apr 29 '15 at 21:39