WPF:如何从 Fonts.SystemFontFamilies 中过滤掉非罗马字体?

发布于 2024-10-13 19:37:43 字数 932 浏览 1 评论 0原文

我知道如何使用几行 XAML 创建 WPF 字体选择器,绑定到 Fonts.SystemFontFamilies (感谢 Norris Cheng 的出色博文),但我不知道如何过滤掉所有国际和其他非罗马字母字体系列。我的用户不太可能需要“Mongolian Baiti”、“Microsoft Uighur”,甚至“Webdings”,当这些字体出现在列表中时,他们就更难找到他们使用的字体想。

SystemFontFamilies 可以访问的对象上是否有任何属性可以用来将非符号罗马字母字体系列与其他字体系列分开?

编辑:我想使用的信息可在 Windows 7 字体控制面板的“设计用途”字段中找到。该字段中的字符串包含“Latin”(表示对拉丁字母应用程序有用的字体)、“国际”字体的其他语言名称(例如,“Mongolian Baiti”是“Mongolian”)以及“Symbol”(表示 Wingdings 等字体)。 “类别”字段对于区分“显示”字体和“文本”字体也很有用。不幸的是,我无法找出从代码中获取此信息的任何方法,我什至无法弄清楚它在 OpenType 规范。我的猜测是 OpenType 脚本标签语言标签

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

旧人 2024-10-20 19:37:43

字体中没有明确的信息来明确定义它是否可读为“罗马”。

您可以借助字形分析来查看字体涵盖的 Unicode 范围。这可以让您了解字体涵盖哪些语言。然而,这也有问题,例如:

  1. 默认的 Windows 7 字体是“Segoe用户界面”。在您的方案中,您会将其视为“罗马”字体吗?这里的问题是,即使您进行字形分析,它也涵盖拉丁语以及其他 Unicode 范围,例如阿拉伯语和泰语。好吧,我们可以包含至少涵盖拉丁语的字体,但是,如果拉丁语范围实际上不像第 3 点中的拉丁语那样可读怎么办?
  2. “蒙古白体”的示例包括覆盖基本拉丁语范围的字形,因此它可以用于呈现“罗马”文本。
  3. Webdings 涵盖拉丁语范围,因此通过分析它可以通过,但它实际上不包含可读的拉丁字符。

字形分析也许可以用来缩小范围,但你可能会得到误报。

更新

我必须在自己的应用程序中处理字体列表,所以不能不管这个! :)

实际上可以通过 GlyphTypeface.Symbol 属性(这对我来说是新的)。因此,通过这个和一些字形分析,以下解决方案应该可以解决问题。

然而,它仍然会找到“Mongolian Baiti”(它是“Baiti”而不是咖喱风格中的“Balti”:)),因为它有拉丁字符的字形,所以它仍然是“罗马”字体,具体取决于您如何定义它。事实上,我的系统上的所有非符号字体都至少具有拉丁字符范围,因此拉丁字形测试实际上并不排除任何字体。

您对“蒙古白提”的具体反对意见是什么?您希望如何自动排除它(例如,不使用手动维护的排除列表)?

[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;
}

更新 2

您可以尝试根据字体对扩展拉丁字符的支持来过滤字体,方法是包含 Latin Extended-ALatin Extended-B< 的过滤器/code> 范围。使用 Latin Extended-ALatin Extended-B 进行过滤会留下很少的字体,但仅对 Latin Extended-A 进行过滤仍然会留下相当多的字体很多字体。它还会自动删除 Mongolian Baiti,因为它仅支持 Latin-1Latin-1 Supplement

这种分析是否能给出理想的结果是非常主观的。需要尝试的东西:

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;
}

同样,非常主观,但以下将提供支持拉丁字形以及国际货币字符子集的字体:

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)
};

更新 3

除了您的问题编辑之外,还有一个版本将与 Windows 7 一起使用。它利用 Window 7 的隐藏字体功能(如 @Rick Sladkey 所指出的),该功能默认隐藏那些被认为对当前用户的区域设置无用的字体。它还将排除符号字体:

[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);
} 

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);
} 
箜明 2024-10-20 19:37:43

我认为如果您不亲自提供的话,您永远不会找到此类信息。字体规范本身不提供此信息,所以我想这意味着您不走运。您最好的选择是确定您认为最终用户“可接受”的字体列表。

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.

单调的奢华 2024-10-20 19:37:43

我找不到一种简单可靠的方法来做到这一点。我认为最好的解决方案是遵循 Windows 7 提供的字体隐藏状态。这样用户可以隐藏/显示他们认为合适的字体,但也可以隐藏所有不适合其所在地区的字体。不幸的是,没有记录的 API 可以查明字体是否隐藏,但您可以使用注册表设置。

这里有一篇文章解释了如何做到这一点:

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:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文