I have added a Google font to my application using the method described here. It's working great!
In the application, users can choose which font to use in some UI elements. This includes system fonts, and now I'd like to add the Google font to this list as well. However I'm struggling to add, or reference, a friendly looking name for this font rather than the entire resource URI pointing to the font itself.
Here is how I currently have the FontFamily resource defined in App.xaml, based on the previously linked StackOverflow answer:
<FontFamily x:Key="Montserrat">pack://application:,,,/Themes/General/Fonts/#Montserrat</FontFamily>
I have a ComboBox defined like so:
<ComboBox x:Name="PART_editor"
Tag="{Binding}"
SelectedValue="{Binding Value}"
IsEnabled="{Binding IsEditable}"
ItemsSource="{Binding Source={x:Static t:MyFonts.MyFontFamilies}, Converter={StaticResource SystemFontFamiliesConverter}}"
VerticalAlignment="Stretch">
The collection it's bound to is like so:
public static class MyFonts
{
private static Collection<FontFamily> myFontFamilies = null;
public static Collection<FontFamily> MyFontFamilies
{
get
{
if (myFontFamilies == null)
{
myFontFamilies = new(Fonts.SystemFontFamilies.ToList());
myFontFamilies.Add(Application.Current.FindResource("Montserrat") as FontFamily);
}
return myFontFamilies;
}
}
}
The converter on the ComboBox is just used to sort FontFamily names, so I'll leave it out unless someone thinks it's relevant.
When the ComboBox options are displayed, you get an ugly Uri listed instead of just "Montserrat":
I have tried something similar to the below where you list the FamilyNames in the XAML, but you're unable to add the URI as the XAML value in this case without a syntax editor.
<FontFamily x:Key="Montserrat">
<FontFamily.FamilyNames>
<System:String x:Key="en-US">Montserrat</System:String>
</FontFamily.FamilyNames>
!!! syntax error
pack://application:,,,/Themes/General/Fonts/#Montserrat
</FontFamily>
So, what's the best way to get this font's friendly name to show in my combobox?
You need to Iterate over the embedded fonts, then extract the name from the Typeface
object.
Here is a working demo project where I added all of the Montserrat
font variations (.tff files) top the projects' .\Resources
folder as Content
and copy if newer
. My .csproj
file looks like:
<ItemGroup>
<Content Include="Resources\Montserrat-Black.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-BlackItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-Bold.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-BoldItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-ExtraBold.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-ExtraBoldItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-ExtraLight.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-ExtraLightItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-Italic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-Light.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-LightItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-Medium.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-MediumItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-SemiBold.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-SemiBoldItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-Thin.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Montserrat-ThinItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
Here is the code-behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string selectedFontName;
private string fontFamilyPath;
private static string uriPath= @"pack://application:,,,/";
private static string resPath = "./resources/";
public MainWindow()
{
InitializeComponent();
GetFontNames();
SetDefontFont();
}
public ObservableCollection<string> FontNames { get; } = new();
public string SelectedFontName
{
get => selectedFontName;
set
{
SetFontFamilyPath(value);
Set(ref selectedFontName, value);
}
}
public string FontFamilyPath
{
get => fontFamilyPath;
set => Set(ref fontFamilyPath, value);
}
private void GetFontNames()
{
foreach (Typeface typeface in Fonts.GetTypefaces(new Uri(uriPath), resPath))
{
string typefaceName = typeface.FontFamily.FamilyNames.First().Value;
foreach (KeyValuePair<XmlLanguage, string> weight in typeface.FaceNames)
{
// get type weight name
string typeWeightName = weight.Value;
// get type weight value
int? typeWeightValue = typeface
.FontFamily
.FamilyTypefaces
.ToList() // convert collection to List for working with Linq
.FirstOrDefault(x => x.Weight.ToString().Equals(weight.Value) &&
x.Weight.ToOpenTypeWeight() > 0)?
.Weight
.ToOpenTypeWeight();
FontNames.Add($"{typefaceName} {typeWeightName}{(typeWeightValue is null ? "" : $" ({typeWeightValue})")}");
}
}
}
private void SetDefontFont()
=> SetFontFamilyPath(FontNames.First());
private void SetFontFamilyPath(string name)
=> FontFamilyPath = $"./Resources/#{name}";
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool Set<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
and here is the Window:
<Window x:Class="WpfEmbeddedGoogleTtfFont.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfEmbeddedGoogleTtfFont"
mc:Ignorable="d" x:Name="Window"
Title="MainWindow" Height="450" Width="800">
<Grid DataContext="{Binding ElementName=Window}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="The quick brown fox..."
FontFamily="{Binding FontFamilyPath}" FontSize="32" />
<ListBox Grid.Row="1"
ItemsSource="{Binding FontNames}"
SelectedItem="{Binding SelectedFontName}"/>
</Grid>
</Window>
The sample project will extract all of the embedded fonts with their weight names + values and display in a list. You can then select the font and the sample text will use it.