diff --git a/src/Avalonia.Base/Media/FontFamily.cs b/src/Avalonia.Base/Media/FontFamily.cs index 722f806cac..aa2ce05bd4 100644 --- a/src/Avalonia.Base/Media/FontFamily.cs +++ b/src/Avalonia.Base/Media/FontFamily.cs @@ -36,6 +36,11 @@ namespace Avalonia.Media throw new ArgumentNullException(nameof(name)); } + if (baseUri != null && !baseUri.IsAbsoluteUri) + { + throw new ArgumentException("Base uri must be an absolute uri.", nameof(baseUri)); + } + var fontSources = GetFontSourceIdentifier(name); FamilyNames = new FamilyNameCollection(fontSources); @@ -46,20 +51,15 @@ namespace Avalonia.Media if (singleSource.Source is Uri source) { - if (baseUri != null && !baseUri.IsAbsoluteUri) + if (source.IsAbsoluteUri) { - throw new ArgumentException("Base uri must be an absolute uri.", nameof(baseUri)); + Key = new FontFamilyKey(source); } - - Key = new FontFamilyKey(source, baseUri); - } - else - { - if(baseUri != null && baseUri.IsAbsoluteUri) + else { - Key = new FontFamilyKey(baseUri); + Key = new FontFamilyKey(source, baseUri); } - } + } } else { @@ -138,7 +138,7 @@ namespace Avalonia.Media var segment = segments[i]; var innerSegments = segment.Split('#'); - FontSourceIdentifier identifier; + FontSourceIdentifier identifier = new FontSourceIdentifier(name, null); switch (innerSegments.Length) { @@ -159,19 +159,19 @@ namespace Avalonia.Media } else { - var source = path.StartsWith("/", StringComparison.Ordinal) - ? new Uri(path, UriKind.Relative) - : new Uri(path, UriKind.RelativeOrAbsolute); - - identifier = new FontSourceIdentifier(innerName, source); - } - - break; - } + if (path.Contains('/') && Uri.TryCreate(path, UriKind.Relative, out var source)) + { + identifier = new FontSourceIdentifier(innerName, source); + } + else + { + if (Uri.TryCreate(path, UriKind.Absolute, out source)) + { + identifier = new FontSourceIdentifier(innerName, source); + } + } + } - default: - { - identifier = new FontSourceIdentifier(name, null); break; } } diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 7310ab19a1..dfce362297 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -273,8 +273,6 @@ namespace Avalonia.Media //Try to match against fallbacks first if (fontFamily?.Key != null) { - var fontUri = fontFamily.Key.Source.EnsureAbsolute(fontFamily.Key.BaseUri); - if (fontFamily.Key is CompositeFontFamilyKey compositeKey) { for (int i = 0; i < compositeKey.Keys.Count; i++) @@ -296,6 +294,8 @@ namespace Avalonia.Media } } + var fontUri = fontFamily.Key.Source.EnsureAbsolute(fontFamily.Key.BaseUri); + if (fontUri.IsFontCollection()) { if (TryGetFontCollection(fontUri, out var fontCollection) && diff --git a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs index 038332c2da..222a514ed1 100644 --- a/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs +++ b/src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs @@ -35,7 +35,7 @@ namespace Avalonia.Media.Fonts { if (glyphTypeface.TryGetGlyph((uint)codepoint, out _)) { - match = new Typeface(new FontFamily(Key, "#" + glyphTypeface.FamilyName), style, weight, stretch); + match = new Typeface(new FontFamily(null, Key.AbsoluteUri + "#" + glyphTypeface.FamilyName), style, weight, stretch); return true; } @@ -57,7 +57,7 @@ namespace Avalonia.Media.Fonts { if (glyphTypeface.TryGetGlyph((uint)codepoint, out _)) { - match = new Typeface(new FontFamily(Key, "#" + glyphTypeface.FamilyName) , style, weight, stretch); + match = new Typeface(new FontFamily(null, Key.AbsoluteUri + "#" + glyphTypeface.FamilyName), style, weight, stretch); return true; } diff --git a/tests/Avalonia.Base.UnitTests/Media/FontFamilyTests.cs b/tests/Avalonia.Base.UnitTests/Media/FontFamilyTests.cs index 73c46a9295..7990fb1426 100644 --- a/tests/Avalonia.Base.UnitTests/Media/FontFamilyTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/FontFamilyTests.cs @@ -121,12 +121,12 @@ namespace Avalonia.Base.UnitTests.Media } [Theory] - [InlineData("resm:Avalonia.Visuals.UnitTests.Assets.Fonts", "#MyFont")] - [InlineData("avares://Avalonia.Visuals.UnitTests/Assets/Fonts", "#MyFont")] + [InlineData(null, "resm:Avalonia.Visuals.UnitTests.Assets.Fonts#MyFont")] + [InlineData("avares://Avalonia.Visuals.UnitTests/Assets/Fonts", "/#MyFont")] [InlineData("avares://Avalonia.Visuals.UnitTests", "/Assets/Fonts#MyFont")] public void Should_Create_FontFamily_From_Uri_With_Base_Uri(string @base, string name) { - var baseUri = new Uri(@base); + var baseUri = @base != null ? new Uri(@base) : null; var fontFamily = new FontFamily(baseUri, name); @@ -134,5 +134,77 @@ namespace Avalonia.Base.UnitTests.Media Assert.NotNull(fontFamily.Key); } + + [InlineData(null, "Arial", "Arial", null)] + [InlineData(null, "resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#Manrope", "Manrope", "resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests")] + [InlineData(null, "avares://Avalonia.Fonts.Inter/Assets#Inter", "Inter", null)] + [InlineData("avares://Avalonia.Fonts.Inter", "/Assets#Inter", "Inter", "avares://Avalonia.Fonts.Inter/Assets")] + [InlineData("avares://ControlCatalog/MainWindow.xaml", "avares://Avalonia.Fonts.Inter/Assets#Inter", "Inter", "avares://Avalonia.Fonts.Inter/Assets")] + [Theory] + public void Should_Parse_FontFamily_With_BaseUri(string baseUri, string s, string expectedName, string expectedUri) + { + var b = baseUri is not null ? new Uri(baseUri) : null; + + expectedUri = expectedUri is not null ? new Uri(expectedUri).AbsoluteUri : null; + + var fontFamily = FontFamily.Parse(s, b); + + Assert.Equal(expectedName, fontFamily.Name); + + var key = fontFamily.Key; + + if (expectedUri is not null) + { + Assert.NotNull(key); + + if (key.BaseUri is not null) + { + Assert.True(key.BaseUri.IsAbsoluteUri); + } + + if (key.BaseUri is null) + { + Assert.NotNull(key.Source); + Assert.True(key.Source.IsAbsoluteUri); + } + + var fontUri = key.BaseUri; + + if (key.Source is Uri sourceUri) + { + if (sourceUri.IsAbsoluteUri) + { + fontUri = sourceUri; + } + else + { + fontUri = new Uri(fontUri, sourceUri); + } + } + + Assert.Equal(expectedUri, fontUri.AbsoluteUri); + } + } + + [InlineData("avares://MyAssembly/", "Some/Path/#FontName", "avares://MyAssembly/Some/Path/"), ] + [InlineData("avares://MyAssembly/", "./Some/Path/#FontName", "avares://MyAssembly/Some/Path/")] + [InlineData("avares://MyAssembly/sub/", "../Some/Path/#FontName", "avares://MyAssembly/Some/Path/")] + [Theory] + public void Should_Parse_Relative_Path(string baseUriString, string path, string expected) + { + var baseUri = new Uri(baseUriString, UriKind.Absolute); + + var fontFamily = FontFamily.Parse(path, baseUri); + + Assert.NotNull(fontFamily.Key); + + Assert.NotNull(fontFamily.Key.BaseUri); + + Assert.NotNull(fontFamily.Key.Source); + + var actual = new Uri(fontFamily.Key.BaseUri, fontFamily.Key.Source); + + Assert.Equal(expected, actual.AbsoluteUri); + } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs index 8057b81ace..da60947815 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs @@ -409,5 +409,23 @@ namespace Avalonia.Skia.UnitTests.Media } } } + + [InlineData("Arial")] + [InlineData("#Arial")] + [Win32Theory("Windows specific font")] + public void Should_Get_SystemFont_With_BaseUri(string name) + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl()))) + { + using (AvaloniaLocator.EnterScope()) + { + var fontFamily = new FontFamily(new Uri("avares://Avalonia.Skia.UnitTests/NotFound"), name); + + var glyphTypeface = new Typeface(fontFamily).GlyphTypeface; + + Assert.Equal("Arial", glyphTypeface.FamilyName); + } + } + } } } diff --git a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs index 78895912db..24599c5a9b 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs @@ -14,11 +14,11 @@ namespace Avalonia.UnitTests private readonly string _defaultFamilyName; private static readonly Typeface _defaultTypeface = - new Typeface(new FontFamily(new Uri("resm:Avalonia.UnitTests.Assets?assembly=Avalonia.UnitTests", UriKind.Absolute), "Noto Mono")); + new Typeface(new FontFamily("resm:Avalonia.UnitTests.Assets?assembly=Avalonia.UnitTests#Noto Mono")); private static readonly Typeface _italicTypeface = - new Typeface(new FontFamily(new Uri("resm:Avalonia.UnitTests.Assets?assembly=Avalonia.UnitTests", UriKind.Absolute), "Noto Sans")); + new Typeface(new FontFamily("resm:Avalonia.UnitTests.Assets?assembly=Avalonia.UnitTests#Noto Sans")); private static readonly Typeface _emojiTypeface = - new Typeface(new FontFamily(new Uri("resm:Avalonia.UnitTests.Assets?assembly=Avalonia.UnitTests"), "Twitter Color Emoji")); + new Typeface(new FontFamily("resm:Avalonia.UnitTests.Assets?assembly=Avalonia.UnitTests#Twitter Color Emoji")); public HarfBuzzFontManagerImpl(string defaultFamilyName = "Noto Mono") {