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

后端 未结 3 1121
逝去的感伤
逝去的感伤 2021-01-12 05:29

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\

相关标签:
3条回答
  • 2021-01-12 05:41

    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.

    0 讨论(0)
  • 2021-01-12 05:41

    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);
    } 
    
    0 讨论(0)
  • 2021-01-12 05:49

    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
    0 讨论(0)
提交回复
热议问题