Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: How to filter out non-Roman fonts from Fonts.SystemFontFamilies?

Tags:

wpf

fonts

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.

like image 461
McKenzieG1 Avatar asked Jan 25 '11 20:01

McKenzieG1


3 Answers

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.:

  1. The default Windows 7 font is "Segoe UI". In your scheme would you see this as a "Roman" font? The problem here is that even if you carry out glyph analysis it covers Latin but also other Unicode ranges e.g. Arabic and Thai. Ok fine, we can include fonts that at least cover Latin, however, what if the Latin range is not actually readable as Latin as in point 3?
  2. The example of "Mongolian Baiti" includes glyphs that cover the basic Latin range, so it can be used to render "Roman" text.
  3. Webdings covers the Latin range, so via analysis it could pass, but it does not actually contain readable Latin characters.

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);
} 
like image 52
Tim Lloyd Avatar answered Nov 12 '22 21:11

Tim Lloyd


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.

like image 1
A.R. Avatar answered Nov 12 '22 20:11

A.R.


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:

  • Honoring Hidden Fonts
like image 1
Rick Sladkey Avatar answered Nov 12 '22 21:11

Rick Sladkey