次の方法で共有


Font Families and Friendly Names

Hi, my name is Niklas Borson and I'm a developer on the WPF Text team. I'm going to write today about font families and in particular the “friendly name” string used to specify a font family.

This may sound like a rather limited topic, but perhaps a couple of short XAML snippets will whet your appetite:

 <TextBox FontFamily="My Font">
    Hello World!
</TextBox>

and

 <TextBox FontFamily="./#My Font">
    Hello World!
</TextBox>

Take a close look at the FontFamily attribute in both cases. The value of this attribute is what we call a friendly name, meaning the string representation of a FontFamily object.

The first friendly name above looks simple enough, but what's with that second one? It looks more like a URI than a family name! Now suppose I also tell you that both examples are valid, both specify font families named “My Font,” yet they are not equivalent.

It turns out that, while a friendly name may be nothing more than the name of font family (that's the friendly part), it may also contain other information for two reasons: (1) to provide a simple way of controlling font fallback and (2) to provide a way of referring to application-defined fonts that are not in the system font collection. To support this, friendly names have a syntax with some reserved characters that need to be escaped, so even if you don't use these features you need to be aware that family names and friendly names cannot be used interchangeably.

What is a font family?

Before delving into details, let's be clear what we mean by font family. A font family is a group of fonts that share the same design. For example, the font family Arial comprises the fonts Arial Regular, Arial Bold, and so on.

Grouping fonts into families makes life easier for users. Imagine if we had only the font faces themselves. If you were editing some text in Arial Regular and wanted to make a word bold, you would have to select from a long list of font faces a bold font that went with Arial Regular (i.e., Arial Bold).

What you'd rather do is say, “Make it bold.” Using font families makes this easy because it allows particular font faces within the same family to be selected based on properties like style, weight, and stretch.

In the WPF API, the font family concept is represented (not surprisingly) by the FontFamily class. The Typeface class combines into one object the bundle of properties necessary to identify a particular font: the font family, style, weight, and stretch. (The Typeface also specifies a fallback font family, about which more later.)

There is another class which represents the font file itself, namely GlyphTypeface, but this is a much lower-level API. GlyphTypeface is used when you're working directly with glyphs rather than characters. It is one of the outputs of the line layout process, whereas Typeface (and therefore FontFamily) are inputs. To use GlyphTypeface you must know the exact location of the font file because GlyphTypeface is constructed from an absolute URI. Nothing I have to say here about friendly names applies to GlyphTypeface.

Font Fallback

If a font family cannot render a character in a text run, the text formatter selects (falls back to) a different font family for that character. You can control this process by specifying a friendly name with multiple family names separated by commas, as follows:

 <TextBox FontFamily="My Font, Comic Sans">
    Hello World!
</TextBox>

This example will look familiar to anyone who has used CSS. It means, “Render the text using My Font if possible; for characters not in My Font use Comic Sans.”

There's more to the story. If neither of the specifies families can render the character then we fall back to the font family specified by the FallbackFontFamily property of the Typeface. Finally, any of the font families in this fallback sequence could be a composite font, which is a special kind of font family that maps languages and code points to other font families. The default value of the FallbackFontFamily property is a composite font named Global User Interface. If you wanted, you could define your own composite font, but that would be a topic for another day.

The use of comma as a delimiter raises the question: what if a family name itself contains a comma? The answer is, literal commas must be doubled in the friendly name. For example, the family name “My Font, Unleashed” might appear in a friendly name thus:

 <TextBox FontFamily="My Font,, Unleashed, Comic Sans">
    Hello World!
</TextBox>

As an aside, people are sometimes tempted to use GlyphTypeface in cases where FontFamily would be a better fit. Font fallback is one of the things you lose by doing this. Font fallback operates at the higher level of font families. A GlyphTypeface represents the specific font file we end up with after font fallback.

Application-Defined Fonts

The other requirement that complicates friendly name syntax is the need to refer to application-defined fonts, by which I mean fonts not in the system font collection. To support this, each family name in a friendly name can be preceded by a location part in the form of a relative URI. The family name then becomes the fragment part of the URI.

Consider an example:

 <TextBox FontFamily="./#My Font,, Unleashed, Comic Sans">
    Hello World!
</TextBox>

Let's introduce a new term, font family reference, to refer to each of the comma-delimited fields. The friendly name above comprises two font family references:

 ./#My Font, Unleashed

and:

 Comic Sans

The second needs little explanation. It has no location part (no '#') and therefore simply denotes a family name in the system font collection.

The first font family reference specifies a family name and the location ./ meaning “current folder”. This begs the question, current relative to what? The answer is, relative to the BaseUri proprety of the FontFamily object. We didn't specify a BaseUri property explicity, but when a FontFamily is created from a XAML attribute (by the appropriate type converter), the BaseUri property is automatically set to the URI of the XAML file itself. As a result, the first font family reference in the example means, “The font family named ‘My Font, Unleashed’ in the same folder as the referring file.”

The above font family reference will work if the XAML file and the font file are in the same file system directory, in which case the base URI specifies the file: scheme. It will also work if both XAML and font file are embedded in the application, in which case the base URI specifies the pack: scheme. These are the only two schemes we support for font family references in the first release of WPF.

Of course the location part of a font family reference doesn't have to be ./. It can be any relative URI reference, including a rooted path like /fonts/. It can also include a file name rather than just a folder, e.g.,:

 <TextBox FontFamily="./myfont.ttf#My Font">
    Hello World!
</TextBox>

When specifying a file name, you need to be aware of a special case illustrated by the following example:

 <TextBox FontFamily="myfont.ttf#My Font">
    Hello World!
</TextBox>

You might expect these last two examples to be equivalent, but they're not. The presence of the ./ before the file name in the first example causes us to look for the font in the current folder relative to the base URI. In the second example, the location part comprises a file name only with no path, and we therefore look for the file in the system font collection. We needed to provide a way of referring to specific files in the default fonts folder becase there are certain fonts that can only be distinguised by file name. (There is a tool that always writes the same hard-coded family name in every font it creates.)

Manipulating Friendly Names

It should by now be clear that a friendly name is not the same thing as a family name — even though they may have the same value in many cases. If your code treats them interchangeably, you may have subtle bugs due to different levels of escaping.

If you have a family name and you want to obtain the corresponding friendly name, you could do the necessary escaping yourself as follows:

 static string FamilyNameToFriendlyName(string familyName)
{
    // Escape characters that are reserved in the fragment 
    // part of a URI, including:
    //  '%' - reserved for escape prefix
    //  '#' - reserved for fragment delimiter
    string fontFamilyReference = familyName.Replace("%", "%25").Replace("#", "%23");

    // Replace single commas in the font family reference 
    // with double commas in the friendly name.
    return fontFamilyReference.Replace(",", ",,");
}

In most cases, the string returned from this function will be the same one you pass in, but your program will now work correctly with weird family names like “Font #10, 30% Off”.

Of course the best way to deal with escaping and unescaping is not to do it yourself at all. If you have a FontFamily object then you don't have to worry about converting between friendly names and family names because you already have access to both values: the Source property is the friendly name, and the FamilyNames property is a dictionary of family names indexed by language.

Similarly, suppose you want to create a list of font families for the user to choose from. The XAML-based font chooser in an earlier blog entry by Norris populated a list of font families by data binding to the Fonts.SystemFontFamilies collection. Alternatively, if you wanted to list fonts at a specific location you would call Fonts.GetFontFamilies(Uri,string) . Either way, you get back a collection of FontFamily so you already have the information you need without having to convert strings from one form to another.

Testing Hint

The previous section showed why it might a good idea to test your app with weird family names. Here I'll show how you can create a font family with any name you like using only a text editor.

Just copy the example below into a text editor, replace YOUR FAMILY NAME HERE with a name of your choosing, and save the file with the extension .CompositeFont in the %windir%\Fonts folder, or whatever folder your application looks in for fonts.

 <FontFamily
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/composite-font"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    Baseline="0.9"
    LineSpacing="1.2">

    <!-- Name mapping -->
    <FontFamily.FamilyNames>
        <System:String x:Key="en-US">YOUR FAMILY NAME HERE</System:String>
    </FontFamily.FamilyNames>

    <!-- Faces to report in font chooser UI -->
    <FontFamily.FamilyTypefaces>
        <FamilyTypeface
            Weight="Normal" Stretch="Normal" Style="Normal"
            UnderlinePosition="-0.1" UnderlineThickness="0.05"
            StrikethroughPosition="0.3" StrikethroughThickness="0.05"
            CapsHeight="0.5" XHeight="0.3" />

        <FamilyTypeface
            Weight="Bold" Stretch="Normal" Style="Normal"
            UnderlinePosition="-0.1" UnderlineThickness="0.05"
            StrikethroughPosition="0.3" StrikethroughThickness="0.05"
            CapsHeight="0.5" XHeight="0.3" />
    </FontFamily.FamilyTypefaces>

    <!-- Character to family lookups (in lookup order) -->
    <FontFamily.FamilyMaps>
        <FontFamilyMap Target="Comic Sans"/>
    </FontFamily.FamilyMaps>
</FontFamily>

The above example is just an edited-down version of one of the composite font files installed by WPF. I changed the family name and got rid of all the FontFamilyMap elements except one, which maps everything to “Comic Sans”. You may have noticed I have a certain fondness for Comic Sans.

If you made it this far, thank you for showing interest. We're pretty excited by the text capabilities of our platform and can't wait to see what you can do with it!

Comments

  • Anonymous
    June 07, 2009
    PingBack from http://greenteafatburner.info/story.php?id=1080

  • Anonymous
    May 12, 2011
    I'm trying to get the Microsoft Avalon Patient Monitoring demo to work in VS 2010 (originally VS 2005); but, have run into a problem with the FontFamily tag.  There is a reference to a font as follows:   <FontFamily x:Key="FontFamilyBrand1" >#Helvetica Neue LT Std 67</FontFamily> When I try to view the XAML in the designer I get the following error:   The File '#Helvetica Neue LT Std 67' is not part of the project or its 'Build Action' is not set to 'Resource' The project has 2 font files as part of the project.  They are HelveticaNeueLTStd-LtCn.otf and HelveticaNeueLTStd-MdCn.otf. From your article, I suspect I may have a path problem.  However, I'm not even sure the font name is entirely correct.  Any help you can provide would be great.