I know how to create a WPF font picker with a few lines of XAML, binding to Fonts.SystemFontFamilies
(thanks to Norris Cheng's excellent blog post), but I can't figure out how to filter out all the international and other non-Roman-alphabet font families. My users aren't likely to need "Mongolian Baiti", "Microsoft Uighur", or even "Webdings", and when those are in the list it makes it harder for them to find the fonts they do want.
Are there any properties on the objects reachable from SystemFontFamilies
that I can use to separate the non-symbol Roman-alphabet font families from the rest?
EDIT: The information I would like to use is available in the Windows 7 Fonts control panel, in the "Designed for" field. This strings in this field contain "Latin" for fonts useful for Latin-alphabet applications, other language names for "international" fonts ('Mongolian Baiti' is 'Mongolian', for example), and "Symbol" for fonts like Wingdings. The "Category" field also looks useful for separating 'display' fonts from 'text' fonts. Unfortunately, I can't figure out any way to get this information from code, and I can't even figure out where it is in the OpenType spec. My guess is that the OpenType script tags or language tags are involved.
There is no explicit information in a font that defines unambiguously whether it is readable as "Roman" or not.
You could resort to glyph analysis to see what Unicode ranges a font covered. This could give you a clue as to what languages a font covered. However, this has problems too, e.g.:
Glyph analysis could be applied to narrow things down perhaps, but you could get false positives.
Update
I have to deal with font lists in my own application, so couldn't leave this one alone! :)
It is actually possible to derive whether a font is a symbol font via the GlyphTypeface.Symbol property (which is new to me). Therefore with this and a bit of glyph analysis, the following solution should do the trick.
It will however still find "Mongolian Baiti" (and it's "Baiti" not "Balti" as in the curry style :)) as this has glyphs for Latin characters so it still is a "Roman" font depending on how you define this. As a matter of fact all non-Symbol fonts on my system have at least the Latin character range, so the Latin glyph test doesn't actually exclude any fonts.
What is your particular objection to "Mongolian Baiti", and how do you expect to automatically exclude it (without using a manually maintained exclusion list for example)?
[Test]
public void test()
{
var fonts = Fonts.SystemFontFamilies.OrderBy(x => x.ToString());
var latinFonts = fonts.Where(f =>
f.Source.StartsWith("Global") ||
(!IsSymbol(f) && HasLatinGlyphs(f)));
latinFonts.ToList().ForEach(Console.WriteLine);
}
private bool IsSymbol(FontFamily fontFamily)
{
GlyphTypeface glyph = GetFirstGlpyhTypeface(fontFamily);
return glyph.Symbol;
}
private bool HasLatinGlyphs(FontFamily fontFamily)
{
GlyphTypeface glyph = GetFirstGlpyhTypeface(fontFamily);
for (int i = 32; i < 127; i++)
{
if (!glyph.CharacterToGlyphMap.ContainsKey(i)) return false;
}
return true;
}
private GlyphTypeface GetFirstGlpyhTypeface(FontFamily fontFamily)
{
Typeface typeface = fontFamily.GetTypefaces().First();
GlyphTypeface glyph;
typeface.TryGetGlyphTypeface(out glyph);
return glyph;
}
Update 2
You could experiment with filtering out fonts based on what support they have for extended Latin characters by including filters for Latin Extended-A
and Latin Extended-B
ranges. Filtering with both Latin Extended-A
and Latin Extended-B
leaves very few fonts left, but just filtering on Latin Extended-A
still leaves quite a lot of fonts. It also automatically removes Mongolian Baiti
as this only has support for Latin-1
and Latin-1 Supplement
.
Whether this sort of analysis gives desirable results is highly subjective. Something to experiment with though:
private bool HasLatinGlyphs(FontFamily fontFamily)
{
GlyphTypeface glyph = GetFirstGlpyhTypeface(fontFamily);
List<Tuple<int, int>> ranges = new List<Tuple<int, int>>
{
new Tuple<int, int>(32, 126), //Latin-1
new Tuple<int, int>(160, 255), //Latin-1 Supplement
new Tuple<int, int>(256, 383), //Latin Extended-A
new Tuple<int, int>(384, 591), //Latin Extended-B
};
foreach (Tuple<int, int> range in ranges)
{
for (int i = range.Item1; i <= range.Item2; i++)
{
if (!glyph.CharacterToGlyphMap.ContainsKey(i)) return false;
}
}
return true;
}
Again, highly subjective, but the following will give fonts that support Latin glyphs plus a sub-set of international currency characters:
List<Tuple<int, int>> ranges = new List<Tuple<int, int>>
{
new Tuple<int, int>(32, 126), //Latin-1
new Tuple<int, int>(0x20A0, 0x20B5), //Currency Symbols (Partial)
};
Update 3
Further to your question edit here is a version that will work with Windows 7. It leverages Window 7's hidden font feature (as pointed out by @Rick Sladkey) which by default hides fonts that are not considered to be useful for the current user's locale setting. It will also exclude symbol fonts:
[Test]
public void test()
{
var allFonts = Fonts.SystemFontFamilies.OrderBy(x => x.Source);
var filteredFonts = allFonts.Where(f =>
IsComposite(f) || (!IsSymbol(f) && !IsHidden(f)));
filteredFonts.ToList().ForEach(Console.WriteLine);
}
private static bool IsComposite(FontFamily fontFamily)
{
return fontFamily.Source.StartsWith("Global");
}
private static bool IsSymbol(FontFamily fontFamily)
{
Typeface typeface = fontFamily.GetTypefaces().First();
GlyphTypeface glyph;
typeface.TryGetGlyphTypeface(out glyph);
return glyph.Symbol;
}
private static bool IsHidden(FontFamily fontFamily)
{
const string Key = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Font Management";
const string Value = "Inactive Fonts";
RegistryKey key = Registry.CurrentUser.OpenSubKey(Key);
IEnumerable<string> hiddenFonts = (string[])key.GetValue(Value);
return hiddenFonts.Contains(fontFamily.Source);
}
I don't think that you are ever going to find that type of information without providing it yourself. Font specifications themselves don't provide this information, so I guess this means you are out of luck. Your best bet is to determine a list of fonts that you deem 'acceptable' for your end users.
I could not find an easy reliable way to do this. I think the best solution is to honor the hidden state of fonts as provided by Windows 7. This way users can hide/show fonts as they see fit but also hide all fonts inappropriate for their region. Unfortunately there is no documented API to find out if a font is hidden or not but you can use a registry setting.
Here is an article that explains how to do it:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With