Browse Source

Make sure GlyphTypefaces are always cached

pull/4627/head
Benedikt Schroeder 5 years ago
parent
commit
04c781ca14
  1. 28
      Avalonia.sln
  2. 4
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  3. 2
      src/Avalonia.Controls/TextBlock.cs
  4. 4
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  5. 12
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  6. 67
      src/Avalonia.Visuals/Media/FontManager.cs
  7. 40
      src/Avalonia.Visuals/Media/Fonts/FontKey.cs
  8. 7
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  9. 31
      src/Avalonia.Visuals/Media/Typeface.cs
  10. 5
      src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
  11. 2
      src/Avalonia.Visuals/Rendering/RendererBase.cs
  12. 7
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  13. 19
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  14. 2
      src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
  15. 7
      src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
  16. 2
      tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
  17. 8
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  18. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  19. 5
      tests/Avalonia.UnitTests/MockFontManagerImpl.cs
  20. 7
      tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs

28
Avalonia.sln

@ -222,9 +222,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.ReactiveUI.Events.UnitTests", "tests\Avalonia.ReactiveUI.Events.UnitTests\Avalonia.ReactiveUI.Events.UnitTests.csproj", "{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events.UnitTests", "tests\Avalonia.ReactiveUI.Events.UnitTests\Avalonia.ReactiveUI.Events.UnitTests.csproj", "{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@ -2040,6 +2040,30 @@ Global
{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhone.Build.0 = Release|Any CPU
{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.Build.0 = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.ActiveCfg = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.Build.0 = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

4
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -282,7 +282,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Constraint = constraint,
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
Typeface = new Typeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize,
Text = text ?? string.Empty,
TextAlignment = TextAlignment,
@ -499,7 +499,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Text = "X",
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
Typeface = new Typeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize,
TextAlignment = TextAlignment,
Constraint = availableSize,

2
src/Avalonia.Controls/TextBlock.cs

@ -452,7 +452,7 @@ namespace Avalonia.Controls
return new TextLayout(
text ?? string.Empty,
FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
new Typeface(FontFamily, FontStyle, FontWeight),
FontSize,
Foreground,
TextAlignment,

4
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -155,9 +155,9 @@ namespace Avalonia.Headless
return new List<string> { "Arial" };
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
{
fontKey = new FontKey("Arial", fontStyle, fontWeight);
typeface = new Typeface("Arial", fontStyle, fontWeight);
return true;
}
}

12
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -1,5 +1,15 @@
Compat issues with assembly Avalonia.Visuals:
MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
CannotSealType : Type 'Avalonia.Media.Typeface' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.Typeface' is a 'struct' in the implementation but is a 'class' in the contract.
CannotMakeMemberNonVirtual : Member 'public System.Boolean Avalonia.Media.Typeface.Equals(System.Object)' is non-virtual in the implementation but is virtual in the contract.
CannotMakeMemberNonVirtual : Member 'public System.Int32 Avalonia.Media.Typeface.GetHashCode()' is non-virtual in the implementation but is virtual in the contract.
TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract.
Total Issues: 3
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract.
Total Issues: 13

67
src/Avalonia.Visuals/Media/FontManager.cs

@ -13,8 +13,8 @@ namespace Avalonia.Media
/// </summary>
public sealed class FontManager
{
private readonly ConcurrentDictionary<FontKey, Typeface> _typefaceCache =
new ConcurrentDictionary<FontKey, Typeface>();
private readonly ConcurrentDictionary<Typeface, GlyphTypeface> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, GlyphTypeface>();
private readonly FontFamily _defaultFontFamily;
public FontManager(IFontManagerImpl platformImpl)
@ -76,79 +76,52 @@ namespace Avalonia.Media
PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
/// <summary>
/// Returns a new typeface, or an existing one if a matching typeface exists.
/// Returns a new <see cref="GlyphTypeface"/>, or an existing one if a matching <see cref="GlyphTypeface"/> exists.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="typeface">The typeface.</param>
/// <returns>
/// The typeface.
/// The <see cref="GlyphTypeface"/>.
/// </returns>
public Typeface GetOrAddTypeface(FontFamily fontFamily, FontStyle fontStyle = FontStyle.Normal,
FontWeight fontWeight = FontWeight.Normal)
public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
{
while (true)
{
if (fontFamily.IsDefault)
if (_glyphTypefaceCache.TryGetValue(typeface, out var glyphTypeface))
{
fontFamily = _defaultFontFamily;
return glyphTypeface;
}
var key = new FontKey(fontFamily.Name, fontStyle, fontWeight);
glyphTypeface = new GlyphTypeface(typeface);
if (_typefaceCache.TryGetValue(key, out var typeface))
if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface))
{
return typeface;
return glyphTypeface;
}
typeface = new Typeface(fontFamily, fontStyle, fontWeight);
if (_typefaceCache.TryAdd(key, typeface))
{
return typeface;
}
if (fontFamily == _defaultFontFamily)
if (typeface.FontFamily == _defaultFontFamily)
{
return null;
}
fontFamily = _defaultFontFamily;
typeface = new Typeface(_defaultFontFamily, typeface.Style, typeface.Weight);
}
}
/// <summary>
/// Tries to match a specified character to a typeface that supports specified font properties.
/// Returns <c>null</c> if no fallback was found.
/// Tries to match a specified character to a <see cref="Typeface"/> that supports specified font properties.
/// </summary>
/// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param>
/// <param name="typeface">The matching <see cref="Typeface"/>.</param>
/// <returns>
/// The matched typeface.
/// <c>True</c>, if the <see cref="FontManager"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns>
public Typeface MatchCharacter(int codepoint,
FontStyle fontStyle = FontStyle.Normal,
FontWeight fontWeight = FontWeight.Normal,
FontFamily fontFamily = null, CultureInfo culture = null)
{
foreach (var cachedTypeface in _typefaceCache.Values)
{
// First try to find a cached typeface by style and weight to avoid redundant glyph index lookup.
if (cachedTypeface.Style == fontStyle && cachedTypeface.Weight == fontWeight
&& cachedTypeface.GlyphTypeface.GetGlyph((uint)codepoint) != 0)
{
return cachedTypeface;
}
}
var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out var key) ?
_typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Style, key.Weight)) :
null;
return matchedTypeface;
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out Typeface typeface) =>
PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out typeface);
}
}

40
src/Avalonia.Visuals/Media/Fonts/FontKey.cs

@ -1,40 +0,0 @@
using System;
namespace Avalonia.Media.Fonts
{
public readonly struct FontKey : IEquatable<FontKey>
{
public FontKey(string familyName, FontStyle style, FontWeight weight)
{
FamilyName = familyName;
Style = style;
Weight = weight;
}
public string FamilyName { get; }
public FontStyle Style { get; }
public FontWeight Weight { get; }
public override int GetHashCode()
{
var hash = FamilyName.GetHashCode();
hash = hash * 31 + (int)Style;
hash = hash * 31 + (int)Weight;
return hash;
}
public override bool Equals(object other)
{
return other is FontKey key && Equals(key);
}
public bool Equals(FontKey other)
{
return FamilyName == other.FamilyName &&
Style == other.Style &&
Weight == other.Weight;
}
}
}

7
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@ -70,10 +70,11 @@ namespace Avalonia.Media.TextFormatting
var codepoint = Codepoint.ReadAt(text, count, out _);
//ToDo: Fix FontFamily fallback
currentTypeface =
FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.FontFamily);
var matchFound =
FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
defaultTypeface.FontFamily, defaultProperties.CultureInfo, out currentTypeface);
if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
if (matchFound && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
{
//Fallback found
return new ShapeableTextCharacters(text.Take(count),

31
src/Avalonia.Visuals/Media/Typeface.cs

@ -8,17 +8,15 @@ namespace Avalonia.Media
/// Represents a typeface.
/// </summary>
[DebuggerDisplay("Name = {FontFamily.Name}, Weight = {Weight}, Style = {Style}")]
public class Typeface : IEquatable<Typeface>
public readonly struct Typeface : IEquatable<Typeface>
{
private GlyphTypeface _glyphTypeface;
/// <summary>
/// Initializes a new instance of the <see cref="Typeface"/> class.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param>
public Typeface([NotNull]FontFamily fontFamily,
public Typeface([NotNull] FontFamily fontFamily,
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal)
{
@ -45,7 +43,7 @@ namespace Avalonia.Media
{
}
public static Typeface Default => FontManager.Current?.GetOrAddTypeface(FontFamily.Default);
public static Typeface Default { get; } = new Typeface(FontFamily.Default);
/// <summary>
/// Gets the font family.
@ -68,7 +66,7 @@ namespace Avalonia.Media
/// <value>
/// The glyph typeface.
/// </value>
public GlyphTypeface GlyphTypeface => _glyphTypeface ?? (_glyphTypeface = new GlyphTypeface(this));
public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
public static bool operator !=(Typeface a, Typeface b)
{
@ -77,32 +75,17 @@ namespace Avalonia.Media
public static bool operator ==(Typeface a, Typeface b)
{
if (ReferenceEquals(a, b))
{
return true;
}
return !(a is null) && a.Equals(b);
return a.Equals(b);
}
public override bool Equals(object obj)
{
if (obj is Typeface typeface)
{
return Equals(typeface);
}
return false;
return obj is Typeface typeface && Equals(typeface);
}
public bool Equals(Typeface other)
{
if (other is null)
{
return false;
}
return FontFamily.Equals(other.FontFamily) && Style == other.Style && Weight == other.Weight;
return FontFamily == other.FontFamily && Style == other.Style && Weight == other.Weight;
}
public override int GetHashCode()

5
src/Avalonia.Visuals/Platform/IFontManagerImpl.cs

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.Fonts;
namespace Avalonia.Platform
{
@ -26,13 +25,13 @@ namespace Avalonia.Platform
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param>
/// <param name="fontKey">The matching font key.</param>
/// <param name="typeface">The matching typeface.</param>
/// <returns>
/// <c>True</c>, if the <see cref="IFontManagerImpl"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns>
bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey);
FontFamily fontFamily, CultureInfo culture, out Typeface typeface);
/// <summary>
/// Creates a glyph typeface.

2
src/Avalonia.Visuals/Rendering/RendererBase.cs

@ -20,7 +20,7 @@ namespace Avalonia.Rendering
_useManualFpsCounting = useManualFpsCounting;
_fpsText = new FormattedText
{
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily.Default),
Typeface = new Typeface(FontFamily.Default),
FontSize = s_fontSize
};
}

7
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
using SkiaSharp;
@ -31,7 +30,7 @@ namespace Avalonia.Skia
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
FontFamily fontFamily, CultureInfo culture, out Typeface fontKey)
{
SKFontStyle skFontStyle;
@ -81,7 +80,7 @@ namespace Avalonia.Skia
continue;
}
fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight);
fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight);
return true;
}
@ -92,7 +91,7 @@ namespace Avalonia.Skia
if (skTypeface != null)
{
fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight);
fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight);
return true;
}

19
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -2,31 +2,28 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using SkiaSharp;
namespace Avalonia.Skia
{
internal class SKTypefaceCollection
{
private readonly ConcurrentDictionary<FontKey, SKTypeface> _typefaces =
new ConcurrentDictionary<FontKey, SKTypeface>();
private readonly ConcurrentDictionary<Typeface, SKTypeface> _typefaces =
new ConcurrentDictionary<Typeface, SKTypeface>();
public void AddTypeface(FontKey key, SKTypeface typeface)
public void AddTypeface(Typeface key, SKTypeface typeface)
{
_typefaces.TryAdd(key, typeface);
}
public SKTypeface Get(Typeface typeface)
{
var key = new FontKey(typeface.FontFamily.Name, typeface.Style, typeface.Weight);
return GetNearestMatch(_typefaces, key);
return GetNearestMatch(_typefaces, typeface);
}
private static SKTypeface GetNearestMatch(IDictionary<FontKey, SKTypeface> typefaces, FontKey key)
private static SKTypeface GetNearestMatch(IDictionary<Typeface, SKTypeface> typefaces, Typeface key)
{
if (typefaces.TryGetValue(new FontKey(key.FamilyName, key.Style, key.Weight), out var typeface))
if (typefaces.TryGetValue(key, out var typeface))
{
return typeface;
}
@ -42,7 +39,7 @@ namespace Avalonia.Skia
{
if (weight - j >= 100)
{
if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight - j)), out typeface))
if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight - j)), out typeface))
{
return typeface;
}
@ -53,7 +50,7 @@ namespace Avalonia.Skia
continue;
}
if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight + j)), out typeface))
if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight + j)), out typeface))
{
return typeface;
}

2
src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs

@ -56,7 +56,7 @@ namespace Avalonia.Skia
continue;
}
var key = new FontKey(fontFamily.Name, typeface.FontSlant.ToAvalonia(),
var key = new Typeface(fontFamily, typeface.FontSlant.ToAvalonia(),
(FontWeight)typeface.FontWeight);
typeFaceCollection.AddTypeface(key, typeface);

7
src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
using SharpDX.DirectWrite;
using FontFamily = Avalonia.Media.FontFamily;
@ -34,7 +33,7 @@ namespace Avalonia.Direct2D1.Media
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
{
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
@ -51,12 +50,12 @@ namespace Avalonia.Direct2D1.Media
var fontFamilyName = font.FontFamily.FamilyNames.GetString(0);
fontKey = new FontKey(fontFamilyName, fontStyle, fontWeight);
typeface = new Typeface(fontFamilyName, fontStyle, fontWeight);
return true;
}
fontKey = default;
typeface = default;
return false;
}

2
tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs

@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
var r = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return r.CreateFormattedText(text,
FontManager.Current.GetOrAddTypeface(fontFamily, fontStyle, fontWeight),
new Typeface(fontFamily, fontStyle, fontWeight),
fontSize,
textAlignment,
wrapping,

8
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@ -39,7 +39,7 @@ namespace Avalonia.Skia.UnitTests.Media
private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out FontKey fontKey)
CultureInfo culture, out Typeface typeface)
{
foreach (var customTypeface in _customTypefaces)
{
@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media
continue;
}
fontKey = new FontKey(customTypeface.FontFamily.Name, fontStyle, fontWeight);
typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight);
return true;
}
@ -56,7 +56,7 @@ namespace Avalonia.Skia.UnitTests.Media
var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
fontKey = new FontKey(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
return true;
}
@ -73,13 +73,13 @@ namespace Avalonia.Skia.UnitTests.Media
skTypeface = typefaceCollection.Get(typeface);
break;
}
case "Noto Sans":
{
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_italicTypeface.FontFamily);
skTypeface = typefaceCollection.Get(typeface);
break;
}
case FontFamily.DefaultFontFamilyName:
case "Noto Mono":
{
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily);

2
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -130,7 +130,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
using (Start())
{
const string text = "1234الدولي";
const string text = "ぁぁぁぁالدولي";
var defaultProperties = new GenericTextRunProperties(Typeface.Default);

5
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
namespace Avalonia.UnitTests
@ -26,9 +25,9 @@ namespace Avalonia.UnitTests
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out FontKey fontKey)
CultureInfo culture, out Typeface fontKey)
{
fontKey = default;
fontKey = new Typeface(_defaultFamilyName);
return false;
}

7
tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs

@ -1,6 +1,5 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Xunit;
@ -15,9 +14,11 @@ namespace Avalonia.Visuals.UnitTests.Media
{
var fontFamily = new FontFamily("MyFont");
var typeface = FontManager.Current.GetOrAddTypeface(fontFamily);
var typeface = new Typeface(fontFamily);
Assert.Same(typeface, FontManager.Current.GetOrAddTypeface(fontFamily));
var glyphTypeface = FontManager.Current.GetOrAddGlyphTypeface(typeface);
Assert.Same(glyphTypeface, FontManager.Current.GetOrAddGlyphTypeface(typeface));
}
}

Loading…
Cancel
Save