Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introdcue font collections #10455

Merged
merged 15 commits into from
Mar 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions samples/ControlCatalog.NetCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Fonts.Inter;
using Avalonia.Headless;
using Avalonia.LogicalTree;
using Avalonia.Threading;
Expand Down Expand Up @@ -124,6 +125,7 @@ public static AppBuilder BuildAvaloniaApp()
EnableIme = true
})
.UseSkia()
.WithInterFont()
.AfterSetup(builder =>
{
builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
Expand Down
2 changes: 1 addition & 1 deletion samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
var fontComboBox = this.Get<ComboBox>("fontComboBox");
fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x));
fontComboBox.Items = FontManager.Current.SystemFonts;
fontComboBox.SelectedIndex = 0;
}
}
Expand Down
130 changes: 99 additions & 31 deletions src/Avalonia.Base/Media/FontManager.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
using Avalonia.Utilities;

namespace Avalonia.Media
{
Expand All @@ -13,9 +15,11 @@ namespace Avalonia.Media
/// </summary>
public sealed class FontManager
{
private readonly ConcurrentDictionary<Typeface, IGlyphTypeface> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, IGlyphTypeface>();
private readonly FontFamily _defaultFontFamily;
internal static Uri SystemFontsKey = new Uri("fonts:SystemFonts");

public const string FontCollectionScheme = "fonts";

private readonly ConcurrentDictionary<Uri, IFontCollection> _fontCollections = new ConcurrentDictionary<Uri, IFontCollection>();
private readonly IReadOnlyList<FontFallback>? _fontFallbacks;

public FontManager(IFontManagerImpl platformImpl)
Expand All @@ -33,9 +37,12 @@ public FontManager(IFontManagerImpl platformImpl)
throw new InvalidOperationException("Default font family name can't be null or empty.");
}

_defaultFontFamily = new FontFamily(DefaultFontFamilyName);
AddFontCollection(new SystemFontCollection(this));
}

/// <summary>
/// Get the current font manager instance.
/// </summary>
public static FontManager Current
{
get
Expand All @@ -57,11 +64,6 @@ public static FontManager Current
}
}

/// <summary>
///
/// </summary>
public IFontManagerImpl PlatformImpl { get; }

/// <summary>
/// Gets the system's default font family's name.
/// </summary>
Expand All @@ -71,41 +73,109 @@ public string DefaultFontFamilyName
}

/// <summary>
/// Get all installed font family names.
/// Get all system fonts.
/// </summary>
/// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) =>
PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
public IFontCollection SystemFonts => _fontCollections[SystemFontsKey];

internal IFontManagerImpl PlatformImpl { get; }

/// <summary>
/// Returns a new <see cref="IGlyphTypeface"/>, or an existing one if a matching <see cref="IGlyphTypeface"/> exists.
/// Tries to get a glyph typeface for specified typeface.
/// </summary>
/// <param name="typeface">The typeface.</param>
/// <param name="glyphTypeface">The created glyphTypeface</param>
/// <returns>
/// The <see cref="IGlyphTypeface"/>.
/// <c>True</c>, if the <see cref="FontManager"/> could create the glyph typeface, <c>False</c> otherwise.
/// </returns>
public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
public bool TryGetGlyphTypeface(Typeface typeface, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
{
while (true)
glyphTypeface = null;

var fontFamily = typeface.FontFamily;

if (fontFamily.Key is FontFamilyKey key)
{
if (_glyphTypefaceCache.TryGetValue(typeface, out var glyphTypeface))
var source = key.Source;

if (!source.IsAbsoluteUri)
{
return glyphTypeface;
if (key.BaseUri == null)
{
throw new NotSupportedException($"{nameof(key.BaseUri)} can't be null.");
}

source = new Uri(key.BaseUri, source);
}

glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface);
if (!_fontCollections.TryGetValue(source, out var fontCollection))
{
var embeddedFonts = new EmbeddedFontCollection(source, source);

embeddedFonts.Initialize(PlatformImpl);

if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface))
if (embeddedFonts.Count > 0 && _fontCollections.TryAdd(source, embeddedFonts))
{
fontCollection = embeddedFonts;
}
}

if (fontCollection != null && fontCollection.TryGetGlyphTypeface(fontFamily.FamilyNames.PrimaryFamilyName,
typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface))
{
return glyphTypeface;
return true;
}

if (typeface.FontFamily == _defaultFontFamily)
if (!fontFamily.FamilyNames.HasFallbacks)
{
throw new InvalidOperationException($"Could not create glyph typeface for: {typeface.FontFamily.Name}.");
return false;
}
}

typeface = new Typeface(_defaultFontFamily, typeface.Style, typeface.Weight);
foreach (var familyName in fontFamily.FamilyNames)
{
if (SystemFonts.TryGetGlyphTypeface(familyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface))
{
return true;
}
}

return SystemFonts.TryGetGlyphTypeface(DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface);
}

/// <summary>
/// Add a font collection to the manager.
/// </summary>
/// <param name="fontCollection">The font collection.</param>
/// <exception cref="ArgumentException"></exception>
/// <remarks>If a font collection's key is already present the collection is replaced.</remarks>
public void AddFontCollection(IFontCollection fontCollection)
{
var key = fontCollection.Key;

if (!fontCollection.Key.IsFontCollection())
{
throw new ArgumentException("Font collection Key should follow the fonts: scheme.", nameof(fontCollection));
}

_fontCollections.AddOrUpdate(key, fontCollection, (_, oldCollection) =>
{
oldCollection.Dispose();

return fontCollection;
});

fontCollection.Initialize(PlatformImpl);
}

/// <summary>
/// Removes the font collection that corresponds to specified key.
/// </summary>
/// <param name="key">The font collection's key.</param>
public void RemoveFontCollection(Uri key)
{
if (_fontCollections.TryRemove(key, out var fontCollection))
{
fontCollection.Dispose();
}
}

Expand All @@ -123,18 +193,16 @@ public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
/// <c>True</c>, if the <see cref="FontManager"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns>
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
FontStretch fontStretch,
FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface)
FontStretch fontStretch, FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface)
{
if(_fontFallbacks != null)
if (_fontFallbacks != null)
{
foreach (var fallback in _fontFallbacks)
{
typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight, fontStretch);

var glyphTypeface = GetOrAddGlyphTypeface(typeface);

if(glyphTypeface.TryGetGlyph((uint)codepoint, out _)){
if (TryGetGlyphTypeface(typeface, out var glyphTypeface) && glyphTypeface.TryGetGlyph((uint)codepoint, out _))
{
return true;
}
}
Expand Down
Loading