Classic VB Corner

Honoring Hidden Fonts

Windows 7 hides a number of locale-inappropriate fonts by default, and allows users to toggle this property as well. Here's how to honor that setting.

Remember the old warnings about not loading too many fonts, as they'd slow the system down? Yeah, you probably ignored those too. And later regretted it, at times, as you scanned your font selection dialogs or dropdowns looking for a particular font. You'd probably also wonder, "what are some of those fonts?!?" Happened to me a lot.

Well, Windows 7 now helps, a bit, with this problem by offering what amounts to a Hidden property for fonts. By default, most locale-inappropriate fonts are set to hidden, and users may also toggle this property from the Fonts dialog in Control Panel. Unfortunately, Microsoft chose to not expose any API to this useful property.

This shouldn't be an issue, according to Microsoft, as the common font dialog hides these fonts for you. But if you want to expose a dropdown list of fonts for users to choose from, like, oh, Microsoft Word, well, tough luck. Hmmm, but looking at both Microsoft Word 2007 and 2010, running under Windows 7, and they are at least so far playing by their own rules, it appears. Pull up WordPad, though, and you'll quickly see that the dropdown font list in that applet is somehow detecting which fonts are hidden and which are not.

So how is WordPad making this determination? On a hunch, I fired up Process Monitor (procmon), and started watching the registry changes while I toggled the hidden property of a few fonts on and off. Bingo! It turns out there's a registry entry that holds a list of all the hidden fonts in Windows 7. You can find the "Inactive Fonts" entry under this key:

\HKCU\Software\Microsoft\Windows NT\CurrentVersion\Font Management

This "Inactive Fonts" entry is stored in the REG_MULTI_SZ format, which is just a series (or list) of null-terminated (vbNullChar) strings. This is probably the easiest list to search, of all, as we can leverage the power of VB's native Instr function like so:

Public Function FontHidden(ByVal FontFamily As String, _
  Optional ByVal ForceRefresh As Boolean = True) As Boolean
  
  Static Initialized As Boolean
  Static fnts As String
  Const Key As String = _
   "Software\Microsoft\Windows NT\CurrentVersion\Font Management"
  
  ' If this is the first time this routine has been called, or a
  ' forced refresh is requested, read hidden fonts from registry.
  If Initialized = False Or ForceRefresh = True Then
   fnts = RegGetStringValue _
     (HKEY_CURRENT_USER, Key, "Inactive Fonts")
   fnts = vbNullChar & fnts
   Initialized = True
  End If
  
  ' Registry string is REG_MULTI_SZ, which means it's nullchar
  ' delimited So we bracket search term with nullchars and do
  ' a case-insensitive scan:
  FontFamily = vbNullChar & Trim$(FontFamily) & vbNullChar
  FontHidden = CBool(InStr(1, fnts, FontFamily, vbTextCompare))
End Function

You can use the registry routine of your choice, of course, to snag that string from the system. The one I used will be available with the code download linked below. The function I wrote persists the registry value, rather than call it each time, on the assumption that it will be recalled repeatedly to filter a long list of fonts. But as you can see, I did provide a means to force a refresh of the list of hidden fonts.

The real meat of the FontHidden function is in the very last line where the return value is assigned. Note that when the list of hidden fonts is retrieved, I add a vbNullChar to the beginning of the string. Now, to search the list for a given substring, we simply bracket the search phrase with vbNullChar's and pass both to Instr. To be safe, I chose the case-insensitive vbTextCompare option. If Instr returns a value other than 0, the search string we're looking for matches an element in the list.

You can now call EnumFontFamiliesEx, and filter the results of that to remove all the hidden fonts by comparing the family name passed in each callback to the "Invalid Fonts" list stored in the registry. You can download the FontFilter sample from my Web site, for a completely coded example, if you'd like.

As a final note, I'll add that you may also want to filter out the fonts that begin with an "@" symbol, as these denote vertical fonts (see Figure 10) that are most typically used in Asian systems. It remains a mystery to me why Microsoft chose to return an attribute of the font by altering the font name, in this case, rather than through some identifiable data element. I'm also unsure why one particular font family, "@Kozuka", isn't typically filtered out by the common font dialog, while all others are. If you have any insight on either of those issues, please leave a comment below or drop me an email.

About the Author

Karl E. Peterson wrote Q&A, Programming Techniques, and various other columns for VBPJ and VSM from 1995 onward, until Classic VB columns were dropped entirely in favor of other languages. Similarly, Karl was a Microsoft BASIC MVP from 1994 through 2005, until such community contributions were no longer deemed valuable. He is the author of VisualStudioMagazine.com's new Classic VB Corner column. You can contact him through his Web site if you'd like to suggest future topics for this column.

comments powered by Disqus

Featured

Subscribe on YouTube