Improve FontCollection customization (#19756)
* Improve FontCollection user story * Make adjustments after review * Refactor IsFontFile * Make FontFamilyLoader internal Make tests happy again * Update baseline * Adjust modifier --------- Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
|
||||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Win32.Interoperability.WinFormsAvaloniaControlHost.PreFilterMessage(System.Windows.Forms.Message@)</Target>
|
||||
<Left>baseline/Avalonia.Win32.Interoperability/lib/net461/Avalonia.Win32.Interoperability.dll</Left>
|
||||
<Right>current/Avalonia.Win32.Interoperability/lib/net461/Avalonia.Win32.Interoperability.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Win32.Interoperability.WinFormsAvaloniaControlHost.PreFilterMessage(System.Windows.Forms.Message@)</Target>
|
||||
<Left>baseline/Avalonia.Win32.Interoperability/lib/net6.0-windows7.0/Avalonia.Win32.Interoperability.dll</Left>
|
||||
<Right>current/Avalonia.Win32.Interoperability/lib/net6.0-windows7.0/Avalonia.Win32.Interoperability.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Win32.Interoperability.WinFormsAvaloniaControlHost.PreFilterMessage(System.Windows.Forms.Message@)</Target>
|
||||
<Left>baseline/Avalonia.Win32.Interoperability/lib/net8.0-windows7.0/Avalonia.Win32.Interoperability.dll</Left>
|
||||
<Right>current/Avalonia.Win32.Interoperability/lib/net8.0-windows7.0/Avalonia.Win32.Interoperability.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0008</DiagnosticId>
|
||||
<Target>T:Avalonia.Win32.Interoperability.WinFormsAvaloniaControlHost</Target>
|
||||
<Left>baseline/Avalonia.Win32.Interoperability/lib/net461/Avalonia.Win32.Interoperability.dll</Left>
|
||||
<Right>current/Avalonia.Win32.Interoperability/lib/net461/Avalonia.Win32.Interoperability.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0008</DiagnosticId>
|
||||
<Target>T:Avalonia.Win32.Interoperability.WinFormsAvaloniaControlHost</Target>
|
||||
<Left>baseline/Avalonia.Win32.Interoperability/lib/net6.0-windows7.0/Avalonia.Win32.Interoperability.dll</Left>
|
||||
<Right>current/Avalonia.Win32.Interoperability/lib/net6.0-windows7.0/Avalonia.Win32.Interoperability.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0008</DiagnosticId>
|
||||
<Target>T:Avalonia.Win32.Interoperability.WinFormsAvaloniaControlHost</Target>
|
||||
<Left>baseline/Avalonia.Win32.Interoperability/lib/net8.0-windows7.0/Avalonia.Win32.Interoperability.dll</Left>
|
||||
<Right>current/Avalonia.Win32.Interoperability/lib/net8.0-windows7.0/Avalonia.Win32.Interoperability.dll</Right>
|
||||
</Suppression>
|
||||
</Suppressions>
|
||||
@@ -1,24 +1,150 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
|
||||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@)</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@)</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Dialogs.Internal.ManagedFileChooserFilterViewModel.#ctor(Avalonia.Platform.Storage.FilePickerFileType)</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Dialogs.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Dialogs.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@)</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Dialogs.Internal.ManagedFileChooserFilterViewModel.#ctor(Avalonia.Platform.Storage.FilePickerFileType)</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.IFontCollection.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@)</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Dialogs.Internal.ManagedFileChooserFilterViewModel.#ctor(Avalonia.Platform.Storage.FilePickerFileType)</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Dialogs.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Dialogs.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@)</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)</Target>
|
||||
@@ -43,6 +169,12 @@
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@)</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)</Target>
|
||||
@@ -97,6 +229,12 @@
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@)</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)</Target>
|
||||
@@ -151,6 +289,12 @@
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@)</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)</Target>
|
||||
@@ -181,4 +325,124 @@
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Count</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Item(System.Int32)</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.GetEnumerator</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>P:Avalonia.Media.Fonts.FontCollectionBase.Count</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>P:Avalonia.Media.Fonts.FontCollectionBase.Item(System.Int32)</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Count</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Item(System.Int32)</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.GetEnumerator</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>P:Avalonia.Media.Fonts.FontCollectionBase.Count</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>P:Avalonia.Media.Fonts.FontCollectionBase.Item(System.Int32)</Target>
|
||||
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Count</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Item(System.Int32)</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.GetEnumerator</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>P:Avalonia.Media.Fonts.FontCollectionBase.Count</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>P:Avalonia.Media.Fonts.FontCollectionBase.Item(System.Int32)</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Count</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Item(System.Int32)</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.GetEnumerator</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>P:Avalonia.Media.Fonts.FontCollectionBase.Count</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0012</DiagnosticId>
|
||||
<Target>P:Avalonia.Media.Fonts.FontCollectionBase.Item(System.Int32)</Target>
|
||||
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
</Suppressions>
|
||||
|
||||
@@ -31,8 +31,6 @@ namespace Avalonia.Media
|
||||
{
|
||||
PlatformImpl = platformImpl;
|
||||
|
||||
AddFontCollection(new SystemFontCollection(this));
|
||||
|
||||
var options = AvaloniaLocator.Current.GetService<FontManagerOptions>();
|
||||
_fontFallbacks = options?.FontFallbacks;
|
||||
_fontFamilyMappings = options?.FontFamilyMappings;
|
||||
@@ -76,7 +74,19 @@ namespace Avalonia.Media
|
||||
/// <summary>
|
||||
/// Get all system fonts.
|
||||
/// </summary>
|
||||
public IFontCollection SystemFonts => _fontCollections[SystemFontsKey];
|
||||
public IFontCollection SystemFonts
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryGetFontCollection(SystemFontsKey, out var fontCollection))
|
||||
{
|
||||
return fontCollection;
|
||||
}
|
||||
|
||||
// Fallback to an empty system font collection
|
||||
return new EmptySystemFontCollection();
|
||||
}
|
||||
}
|
||||
|
||||
internal IFontManagerImpl PlatformImpl { get; }
|
||||
|
||||
@@ -93,12 +103,13 @@ namespace Avalonia.Media
|
||||
glyphTypeface = null;
|
||||
|
||||
var fontFamily = GetMappedFontFamily(typeface.FontFamily);
|
||||
|
||||
|
||||
if (typeface.FontFamily.Name == FontFamily.DefaultFontFamilyName)
|
||||
{
|
||||
return TryGetGlyphTypeface(new Typeface(DefaultFontFamily, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
|
||||
}
|
||||
|
||||
|
||||
if (fontFamily.Key != null)
|
||||
{
|
||||
if (fontFamily.Key is CompositeFontFamilyKey compositeKey)
|
||||
@@ -167,7 +178,7 @@ namespace Avalonia.Media
|
||||
|
||||
FontFamily GetMappedFontFamily(FontFamily fontFamily)
|
||||
{
|
||||
if (_fontFamilyMappings == null ||!_fontFamilyMappings.TryGetValue(fontFamily.FamilyNames.PrimaryFamilyName, out var mappedFontFamily))
|
||||
if (_fontFamilyMappings == null || !_fontFamilyMappings.TryGetValue(fontFamily.FamilyNames.PrimaryFamilyName, out var mappedFontFamily))
|
||||
{
|
||||
return fontFamily;
|
||||
}
|
||||
@@ -222,8 +233,6 @@ namespace Avalonia.Media
|
||||
|
||||
return fontCollection;
|
||||
});
|
||||
|
||||
fontCollection.Initialize(PlatformImpl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -288,7 +297,7 @@ namespace Avalonia.Media
|
||||
|
||||
if (TryGetFontCollection(source, out var fontCollection) &&
|
||||
// With composite fonts we need to first check if the font collection contains the family if not we skip it
|
||||
fontCollection.TryGetGlyphTypeface(familyName, fontStyle, fontWeight, fontStretch, out _) &&
|
||||
fontCollection.TryGetGlyphTypeface(familyName, fontStyle, fontWeight, fontStretch, out _) &&
|
||||
fontCollection.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, familyName, culture, out typeface))
|
||||
{
|
||||
return true;
|
||||
@@ -319,7 +328,7 @@ namespace Avalonia.Media
|
||||
|
||||
if (key == null)
|
||||
{
|
||||
if(SystemFonts is IFontCollection2 fontCollection2)
|
||||
if (SystemFonts is IFontCollection2 fontCollection2)
|
||||
{
|
||||
if (fontCollection2.TryGetFamilyTypefaces(fontFamily.Name, out var familyTypefaces))
|
||||
{
|
||||
@@ -352,15 +361,23 @@ namespace Avalonia.Media
|
||||
source = SystemFontsKey;
|
||||
}
|
||||
|
||||
if (!_fontCollections.TryGetValue(source, out fontCollection) && (source.IsAbsoluteResm() || source.IsAvares()))
|
||||
if (!_fontCollections.TryGetValue(source, out fontCollection))
|
||||
{
|
||||
var embeddedFonts = new EmbeddedFontCollection(source, source);
|
||||
|
||||
embeddedFonts.Initialize(PlatformImpl);
|
||||
|
||||
if (embeddedFonts.Count > 0 && _fontCollections.TryAdd(source, embeddedFonts))
|
||||
if (source == SystemFontsKey)
|
||||
{
|
||||
fontCollection = embeddedFonts;
|
||||
fontCollection = new SystemFontCollection(PlatformImpl);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (source.IsAbsoluteResm() || source.IsAvares())
|
||||
{
|
||||
fontCollection = new EmbeddedFontCollection(source, source);
|
||||
}
|
||||
}
|
||||
|
||||
if (fontCollection != null)
|
||||
{
|
||||
return _fontCollections.TryAdd(fontCollection.Key, fontCollection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace Avalonia.Media.Fonts
|
||||
{
|
||||
public class EmbeddedFontCollection : FontCollectionBase, IFontCollection2
|
||||
public class EmbeddedFontCollection : FontCollectionBase
|
||||
{
|
||||
private readonly List<FontFamily> _fontFamilies = new List<FontFamily>(1);
|
||||
|
||||
private readonly Uri _key;
|
||||
|
||||
private readonly Uri _source;
|
||||
|
||||
public EmbeddedFontCollection(Uri key, Uri source)
|
||||
@@ -20,152 +12,10 @@ namespace Avalonia.Media.Fonts
|
||||
_key = key;
|
||||
|
||||
_source = source;
|
||||
|
||||
TryAddFontSource(_source);
|
||||
}
|
||||
|
||||
public override Uri Key => _key;
|
||||
|
||||
public override FontFamily this[int index] => _fontFamilies[index];
|
||||
|
||||
public override int Count => _fontFamilies.Count;
|
||||
|
||||
public override void Initialize(IFontManagerImpl fontManager)
|
||||
{
|
||||
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
|
||||
|
||||
var fontAssets = FontFamilyLoader.LoadFontAssets(_source);
|
||||
|
||||
foreach (var fontAsset in fontAssets)
|
||||
{
|
||||
var stream = assetLoader.Open(fontAsset);
|
||||
|
||||
if (fontManager.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
|
||||
{
|
||||
AddGlyphTypeface(glyphTypeface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
|
||||
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
var typeface = GetImplicitTypeface(new Typeface(familyName, style, weight, stretch), out familyName);
|
||||
|
||||
style = typeface.Style;
|
||||
|
||||
weight = typeface.Weight;
|
||||
|
||||
stretch = typeface.Stretch;
|
||||
|
||||
var key = new FontCollectionKey(style, weight, stretch);
|
||||
|
||||
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
|
||||
{
|
||||
if (glyphTypefaces.TryGetValue(key, out glyphTypeface) && glyphTypeface != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
|
||||
{
|
||||
var matchedKey = new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
|
||||
|
||||
if(matchedKey != key)
|
||||
{
|
||||
//Create a synthetic glyph typeface. The successfull result will be cached.
|
||||
if (TryCreateSyntheticGlyphTypeface(glyphTypeface, style, weight, stretch, out var syntheticGlyphTypeface))
|
||||
{
|
||||
glyphTypeface = syntheticGlyphTypeface;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Add the matched glyph typeface to the cache
|
||||
glyphTypefaces.TryAdd(key, glyphTypeface);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Try to find a partially matching font
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var fontFamily = _fontFamilies[i];
|
||||
|
||||
if (fontFamily.Name.ToLower(CultureInfo.InvariantCulture).StartsWith(familyName.ToLower(CultureInfo.InvariantCulture)))
|
||||
{
|
||||
if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out glyphTypefaces) &&
|
||||
TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glyphTypeface = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override IEnumerator<FontFamily> GetEnumerator() => _fontFamilies.GetEnumerator();
|
||||
|
||||
private void AddGlyphTypeface(IGlyphTypeface glyphTypeface)
|
||||
{
|
||||
if (glyphTypeface is IGlyphTypeface2 glyphTypeface2)
|
||||
{
|
||||
//Add the TypographicFamilyName to the cache
|
||||
if (!string.IsNullOrEmpty(glyphTypeface2.TypographicFamilyName))
|
||||
{
|
||||
AddGlyphTypefaceByFamilyName(glyphTypeface2.TypographicFamilyName, glyphTypeface);
|
||||
}
|
||||
|
||||
foreach (var kvp in glyphTypeface2.FamilyNames)
|
||||
{
|
||||
AddGlyphTypefaceByFamilyName(kvp.Value, glyphTypeface);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddGlyphTypefaceByFamilyName(glyphTypeface.FamilyName, glyphTypeface);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypeface)
|
||||
{
|
||||
var typefaces = _glyphTypefaceCache.GetOrAdd(familyName,
|
||||
x =>
|
||||
{
|
||||
_fontFamilies.Add(new FontFamily(_key, familyName));
|
||||
|
||||
return new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>();
|
||||
});
|
||||
|
||||
typefaces.TryAdd(
|
||||
new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch),
|
||||
glyphTypeface);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
|
||||
{
|
||||
familyTypefaces = null;
|
||||
|
||||
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
|
||||
{
|
||||
var typefaces = new List<Typeface>(glyphTypefaces.Count);
|
||||
|
||||
foreach (var key in glyphTypefaces.Keys)
|
||||
{
|
||||
typefaces.Add(new Typeface(new FontFamily(_key, familyName), key.Style, key.Weight, key.Stretch));
|
||||
}
|
||||
|
||||
familyTypefaces = typefaces;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Avalonia.Media.Fonts
|
||||
{
|
||||
internal class EmptySystemFontCollection : FontCollectionBase
|
||||
{
|
||||
public override Uri Key => FontManager.SystemFontsKey;
|
||||
}
|
||||
}
|
||||
@@ -4,30 +4,41 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Utilities;
|
||||
|
||||
namespace Avalonia.Media.Fonts
|
||||
{
|
||||
public abstract class FontCollectionBase : IFontCollection
|
||||
public abstract class FontCollectionBase : IFontCollection2
|
||||
{
|
||||
protected readonly ConcurrentDictionary<string, ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>> _glyphTypefaceCache = new();
|
||||
private static readonly Comparer<FontFamily> FontFamilyNameComparer =
|
||||
Comparer<FontFamily>.Create((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Make this internal for testing purposes
|
||||
internal readonly ConcurrentDictionary<string, ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>> _glyphTypefaceCache = new();
|
||||
|
||||
private readonly object _fontFamiliesLock = new();
|
||||
private volatile FontFamily[] _fontFamilies = Array.Empty<FontFamily>();
|
||||
private readonly IFontManagerImpl _fontManagerImpl;
|
||||
private readonly IAssetLoader _assetLoader;
|
||||
|
||||
protected FontCollectionBase()
|
||||
{
|
||||
_fontManagerImpl = AvaloniaLocator.Current.GetRequiredService<IFontManagerImpl>();
|
||||
_assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
|
||||
}
|
||||
|
||||
public abstract Uri Key { get; }
|
||||
|
||||
public abstract int Count { get; }
|
||||
public int Count => _fontFamilies.Length;
|
||||
|
||||
public abstract FontFamily this[int index] { get; }
|
||||
|
||||
public abstract bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch,
|
||||
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface);
|
||||
public FontFamily this[int index] => _fontFamilies[index];
|
||||
|
||||
public virtual bool TryMatchCharacter(int codepoint, FontStyle style, FontWeight weight, FontStretch stretch,
|
||||
string? familyName, CultureInfo? culture, out Typeface match)
|
||||
{
|
||||
match = default;
|
||||
|
||||
|
||||
//If a font family is defined we try to find a match inside that family first
|
||||
if (familyName != null && _glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
|
||||
{
|
||||
@@ -45,7 +56,7 @@ namespace Avalonia.Media.Fonts
|
||||
//Try to find a match in any font family
|
||||
foreach (var pair in _glyphTypefaceCache)
|
||||
{
|
||||
if(pair.Key == familyName)
|
||||
if (pair.Key == familyName)
|
||||
{
|
||||
//We already tried this before
|
||||
continue;
|
||||
@@ -57,7 +68,11 @@ namespace Avalonia.Media.Fonts
|
||||
{
|
||||
if (glyphTypeface.TryGetGlyph((uint)codepoint, out _))
|
||||
{
|
||||
match = new Typeface(new FontFamily(null, Key.AbsoluteUri + "#" + glyphTypeface.FamilyName), style, weight, stretch);
|
||||
// Found a match
|
||||
match = new Typeface(new FontFamily(null, Key.AbsoluteUri + "#" + glyphTypeface.FamilyName),
|
||||
glyphTypeface.Style,
|
||||
glyphTypeface.Weight,
|
||||
glyphTypeface.Stretch);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -69,9 +84,9 @@ namespace Avalonia.Media.Fonts
|
||||
|
||||
public virtual bool TryCreateSyntheticGlyphTypeface(
|
||||
IGlyphTypeface glyphTypeface,
|
||||
FontStyle style,
|
||||
FontWeight weight,
|
||||
FontStretch stretch,
|
||||
FontStyle style,
|
||||
FontWeight weight,
|
||||
FontStretch stretch,
|
||||
[NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface)
|
||||
{
|
||||
syntheticGlyphTypeface = null;
|
||||
@@ -82,8 +97,6 @@ namespace Avalonia.Media.Fonts
|
||||
return false;
|
||||
}
|
||||
|
||||
var fontManager = FontManager.Current.PlatformImpl;
|
||||
|
||||
var key = new FontCollectionKey(style, weight, stretch);
|
||||
|
||||
var currentKey =
|
||||
@@ -115,17 +128,17 @@ namespace Avalonia.Media.Fonts
|
||||
{
|
||||
using (stream)
|
||||
{
|
||||
if (fontManager.TryCreateGlyphTypeface(stream, fontSimulations, out syntheticGlyphTypeface))
|
||||
if (_fontManagerImpl.TryCreateGlyphTypeface(stream, fontSimulations, out syntheticGlyphTypeface))
|
||||
{
|
||||
//Add the TypographicFamilyName to the cache
|
||||
if (!string.IsNullOrEmpty(glyphTypeface2.TypographicFamilyName))
|
||||
{
|
||||
AddGlyphTypefaceByFamilyName(glyphTypeface2.TypographicFamilyName, syntheticGlyphTypeface);
|
||||
TryAddGlyphTypeface(glyphTypeface2.TypographicFamilyName, key, syntheticGlyphTypeface);
|
||||
}
|
||||
|
||||
foreach (var kvp in glyphTypeface2.FamilyNames)
|
||||
{
|
||||
AddGlyphTypefaceByFamilyName(kvp.Value, syntheticGlyphTypeface);
|
||||
TryAddGlyphTypeface(kvp.Value, key, syntheticGlyphTypeface);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -136,46 +149,420 @@ namespace Avalonia.Media.Fonts
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypeface)
|
||||
public IEnumerator<FontFamily> GetEnumerator() => ((IEnumerable<FontFamily>)_fontFamilies).GetEnumerator();
|
||||
|
||||
public virtual bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
|
||||
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
var typeface = new Typeface(familyName, style, weight, stretch).Normalize(out familyName);
|
||||
|
||||
style = typeface.Style;
|
||||
|
||||
weight = typeface.Weight;
|
||||
|
||||
stretch = typeface.Stretch;
|
||||
|
||||
var key = new FontCollectionKey(style, weight, stretch);
|
||||
|
||||
return TryGetGlyphTypeface(familyName, key, out glyphTypeface);
|
||||
}
|
||||
|
||||
public virtual bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
|
||||
{
|
||||
familyTypefaces = null;
|
||||
|
||||
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
|
||||
{
|
||||
var typefaces = _glyphTypefaceCache.GetOrAdd(familyName,
|
||||
x =>
|
||||
{
|
||||
return new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>();
|
||||
});
|
||||
// Take a snapshot of the entries to avoid issues with concurrent modifications
|
||||
var entries = glyphTypefaces.ToArray();
|
||||
|
||||
typefaces.TryAdd(key, glyphTypeface);
|
||||
var typefaces = new Typeface[entries.Length];
|
||||
|
||||
for (var i = 0; i < entries.Length; i++)
|
||||
{
|
||||
var key = entries[i].Key;
|
||||
|
||||
typefaces[i] = new Typeface(new FontFamily(Key + "#" + familyName), key.Style, key.Weight, key.Stretch);
|
||||
}
|
||||
|
||||
familyTypefaces = typefaces;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetNearestMatch(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
if (!_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
|
||||
{
|
||||
glyphTypeface = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = new FontCollectionKey { Style = style, Weight = weight, Stretch = stretch };
|
||||
|
||||
return TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to add the specified <see cref="IGlyphTypeface"/> to the font collection.
|
||||
/// </summary>
|
||||
/// <remarks>This method checks the <see cref="IGlyphTypeface.FamilyName"/> and, if applicable,
|
||||
/// the typographic family name and other family names provided by the <see cref="IGlyphTypeface2"/> interface.
|
||||
/// If any of these names can be associated with the glyph typeface, the typeface is added to the collection.
|
||||
/// The method ensures that duplicate entries are not added.</remarks>
|
||||
/// <param name="glyphTypeface">The glyph typeface to add. Must not be <see langword="null"/> and must have a non-empty <see
|
||||
/// cref="IGlyphTypeface.FamilyName"/>.</param>
|
||||
/// <returns><see langword="true"/> if the glyph typeface was successfully added to the collection; otherwise, <see
|
||||
/// langword="false"/>.</returns>
|
||||
public bool TryAddGlyphTypeface(IGlyphTypeface glyphTypeface)
|
||||
{
|
||||
if (glyphTypeface == null || string.IsNullOrEmpty(glyphTypeface.FamilyName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
|
||||
|
||||
if (glyphTypeface is IGlyphTypeface2 glyphTypeface2)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
//Add the TypographicFamilyName to the cache
|
||||
if (!string.IsNullOrEmpty(glyphTypeface2.TypographicFamilyName))
|
||||
{
|
||||
if (TryAddGlyphTypeface(glyphTypeface2.TypographicFamilyName, key, glyphTypeface))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kvp in glyphTypeface2.FamilyNames)
|
||||
{
|
||||
if (TryAddGlyphTypeface(kvp.Value, key, glyphTypeface))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TryAddGlyphTypeface(glyphTypeface.FamilyName, key, glyphTypeface);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void Initialize(IFontManagerImpl fontManager);
|
||||
|
||||
public abstract IEnumerator<FontFamily> GetEnumerator();
|
||||
|
||||
void IDisposable.Dispose()
|
||||
/// <summary>
|
||||
/// Attempts to add a glyph typeface from the specified font stream.
|
||||
/// </summary>
|
||||
/// <remarks>The method first attempts to create a glyph typeface from the provided font stream.
|
||||
/// If successful, it adds the created glyph typeface to the collection.</remarks>
|
||||
/// <param name="stream">The font stream containing the font data. The stream must be readable and positioned at the beginning of the
|
||||
/// font data.</param>
|
||||
/// <param name="glyphTypeface">When this method returns, contains the created <see cref="IGlyphTypeface"/> instance if the operation
|
||||
/// succeeds; otherwise, <see langword="null"/>.</param>
|
||||
/// <returns><see langword="true"/> if the glyph typeface was successfully created and added; otherwise, <see
|
||||
/// langword="false"/>.</returns>
|
||||
public bool TryAddGlyphTypeface(Stream stream, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
foreach (var glyphTypefaces in _glyphTypefaceCache.Values)
|
||||
if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out glyphTypeface))
|
||||
{
|
||||
foreach (var pair in glyphTypefaces)
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryAddGlyphTypeface(glyphTypeface);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to add a font source to the font collection.
|
||||
/// </summary>
|
||||
/// <remarks>This method processes the specified font source and attempts to load all available
|
||||
/// fonts from it. Fonts are added to the collection based on their family name and typographic family name (if
|
||||
/// available). If the <paramref name="source"/> is <see langword="null"/>, the method returns <see
|
||||
/// langword="false"/>.</remarks>
|
||||
/// <param name="source">The URI of the font source to add. This can be a file path, a resource URI, or another valid font source
|
||||
/// URI.</param>
|
||||
/// <returns><see langword="true"/> if at least one font from the specified source was successfully added to the font
|
||||
/// collection; otherwise, <see langword="false"/>.</returns>
|
||||
public bool TryAddFontSource(Uri source)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = false;
|
||||
|
||||
switch (source.Scheme)
|
||||
{
|
||||
case "avares":
|
||||
case "resm":
|
||||
{
|
||||
var fontAssets = FontFamilyLoader.LoadFontAssets(source);
|
||||
|
||||
foreach (var fontAsset in fontAssets)
|
||||
{
|
||||
var stream = _assetLoader.Open(fontAsset);
|
||||
|
||||
if (!_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
|
||||
|
||||
//Add TypographicFamilyName to the cache
|
||||
if (glyphTypeface is IGlyphTypeface2 glyphTypeface2 && !string.IsNullOrEmpty(glyphTypeface2.TypographicFamilyName))
|
||||
{
|
||||
if (TryAddGlyphTypeface(glyphTypeface2.TypographicFamilyName, key, glyphTypeface))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (TryAddGlyphTypeface(glyphTypeface.FamilyName, key, glyphTypeface))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "file":
|
||||
{
|
||||
// If the path is a file, load the font file directly
|
||||
if (FontFamilyLoader.IsFontSource(source))
|
||||
{
|
||||
if (!File.Exists(source.LocalPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using var stream = File.OpenRead(source.LocalPath);
|
||||
|
||||
if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
|
||||
{
|
||||
if (TryAddGlyphTypeface(glyphTypeface))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the path is a directory, load all font files from that directory
|
||||
else
|
||||
{
|
||||
if (!Directory.Exists(source.LocalPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var file in Directory.EnumerateFiles(source.LocalPath))
|
||||
{
|
||||
if (FontFamilyLoader.IsFontFile(file))
|
||||
{
|
||||
using var stream = File.OpenRead(file);
|
||||
|
||||
if (_fontManagerImpl.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
|
||||
{
|
||||
if (TryAddGlyphTypeface(glyphTypeface))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
//Unsupported scheme
|
||||
return false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the specified font family into the internal collection, maintaining the collection in sorted order
|
||||
/// by font family name.
|
||||
/// </summary>
|
||||
/// <remarks>If a font family with the same name already exists in the collection, the new
|
||||
/// instance will be inserted alongside it. The collection remains sorted after insertion.</remarks>
|
||||
/// <param name="fontFamily">The font family to add to the collection. Cannot be null.</param>
|
||||
protected void AddFontFamily(FontFamily fontFamily)
|
||||
{
|
||||
if (fontFamily == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fontFamily));
|
||||
}
|
||||
|
||||
lock (_fontFamiliesLock)
|
||||
{
|
||||
var current = _fontFamilies;
|
||||
int index = Array.BinarySearch(current, fontFamily, FontFamilyNameComparer);
|
||||
|
||||
// If an existing family with the same name is present, do nothing
|
||||
if (index >= 0)
|
||||
{
|
||||
pair.Value?.Dispose();
|
||||
// BinarySearch found an equal entry, so avoid
|
||||
// allocating a new array and inserting a duplicate.
|
||||
return;
|
||||
}
|
||||
|
||||
index = ~index;
|
||||
|
||||
var copy = new FontFamily[current.Length + 1];
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
Array.Copy(current, 0, copy, 0, index);
|
||||
}
|
||||
|
||||
copy[index] = fontFamily;
|
||||
|
||||
if (index < current.Length)
|
||||
{
|
||||
Array.Copy(current, index, copy, index + 1, current.Length - index);
|
||||
}
|
||||
|
||||
// Publish new array for readers
|
||||
_fontFamilies = copy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve a glyph typeface that matches the specified font family name and font collection key.
|
||||
/// </summary>
|
||||
/// <remarks>This method performs a binary search to locate font families with names that match
|
||||
/// the specified <paramref name="familyName"/>. If multiple matches are found, the method iterates over them to
|
||||
/// find the best match based on the provided <paramref name="key"/>.</remarks>
|
||||
/// <param name="familyName">The name of the font family to search for. This parameter is case-insensitive.</param>
|
||||
/// <param name="key">The key representing the desired font collection attributes.</param>
|
||||
/// <param name="glyphTypeface">When this method returns, contains the matching <see cref="IGlyphTypeface"/> if a match is found; otherwise,
|
||||
/// <see langword="null"/>.</param>
|
||||
/// <returns><see langword="true"/> if a matching glyph typeface is found; otherwise, <see langword="false"/>.</returns>
|
||||
protected bool TryGetGlyphTypeface(string familyName, FontCollectionKey key, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
glyphTypeface = null;
|
||||
|
||||
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
|
||||
{
|
||||
if (glyphTypefaces.TryGetValue(key, out glyphTypeface) && glyphTypeface != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
|
||||
{
|
||||
var matchedKey = new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
|
||||
|
||||
if (matchedKey != key)
|
||||
{
|
||||
if (TryCreateSyntheticGlyphTypeface(glyphTypeface, key.Style, key.Weight, key.Stretch, out var syntheticGlyphTypeface))
|
||||
{
|
||||
glyphTypeface = syntheticGlyphTypeface;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cache the nearest match for future lookups
|
||||
TryAddGlyphTypeface(familyName, key, glyphTypeface);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
// Binary search for the first possible prefix match using the snapshot array
|
||||
var snapshot = _fontFamilies;
|
||||
int left = 0;
|
||||
int right = snapshot.Length - 1;
|
||||
int firstMatch = -1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
|
||||
var compare = string.Compare(snapshot[mid].Name, familyName, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// If the current name is lexicographically less than the search name, move right
|
||||
if (compare < 0)
|
||||
{
|
||||
left = mid + 1;
|
||||
}
|
||||
else if (compare == 0)
|
||||
{
|
||||
// Exact match found in snapshot. Use the exact family name for lookup
|
||||
if (_glyphTypefaceCache.TryGetValue(snapshot[mid].Name, out var exactGlyphTypefaces) &&
|
||||
TryGetNearestMatch(exactGlyphTypefaces, key, out glyphTypeface))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exact family present but no matching typeface found.
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only check for prefix when snapshot[mid].Name is > familyName. This
|
||||
// avoids the more expensive StartsWith call for names that are definitely
|
||||
// ordered before the search term.
|
||||
if (snapshot[mid].Name.StartsWith(familyName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
firstMatch = mid;
|
||||
right = mid - 1; // Continue searching to the left for the first match
|
||||
}
|
||||
else
|
||||
{
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstMatch != -1)
|
||||
{
|
||||
// Iterate over all consecutive prefix matches
|
||||
for (int i = firstMatch; i < snapshot.Length; i++)
|
||||
{
|
||||
var fontFamily = snapshot[i];
|
||||
|
||||
if (!fontFamily.Name.StartsWith(familyName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out glyphTypefaces) &&
|
||||
TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
internal static bool TryGetNearestMatch(
|
||||
ConcurrentDictionary<FontCollectionKey,
|
||||
IGlyphTypeface?> glyphTypefaces,
|
||||
FontCollectionKey key,
|
||||
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the nearest matching <see cref="IGlyphTypeface"/> for the specified font key from the
|
||||
/// provided collection of glyph typefaces.
|
||||
/// </summary>
|
||||
/// <remarks>This method attempts to find the best match for the specified font key by considering
|
||||
/// various fallback strategies, such as normalizing the font style, stretch, and weight. If no suitable match is found, the method will return the first available non-null <see cref="IGlyphTypeface"/> from the
|
||||
/// collection, if any.</remarks>
|
||||
/// <param name="glyphTypefaces">A collection of glyph typefaces, indexed by <see cref="FontCollectionKey"/>.</param>
|
||||
/// <param name="key">The <see cref="FontCollectionKey"/> representing the desired font attributes.</param>
|
||||
/// <param name="glyphTypeface">When this method returns, contains the <see cref="IGlyphTypeface"/> that most closely matches the specified
|
||||
/// key, if a match is found; otherwise, <see langword="null"/>.</param>
|
||||
/// <returns><see langword="true"/> if a matching <see cref="IGlyphTypeface"/> is found; otherwise, <see
|
||||
/// langword="false"/>.</returns>
|
||||
protected bool TryGetNearestMatch(IDictionary<FontCollectionKey, IGlyphTypeface?> glyphTypefaces, FontCollectionKey key, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
if (glyphTypefaces.TryGetValue(key, out glyphTypeface) && glyphTypeface != null)
|
||||
{
|
||||
@@ -218,7 +605,7 @@ namespace Avalonia.Media.Fonts
|
||||
//Take the first glyph typeface we can find.
|
||||
foreach (var typeface in glyphTypefaces.Values)
|
||||
{
|
||||
if(typeface != null)
|
||||
if (typeface != null)
|
||||
{
|
||||
glyphTypeface = typeface;
|
||||
|
||||
@@ -229,11 +616,86 @@ namespace Avalonia.Media.Fonts
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryFindStretchFallback(
|
||||
ConcurrentDictionary<FontCollectionKey,
|
||||
IGlyphTypeface?> glyphTypefaces,
|
||||
FontCollectionKey key,
|
||||
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
/// <summary>
|
||||
/// Attempts to add a glyph typeface to the cache for the specified font family and key.
|
||||
/// </summary>
|
||||
/// <remarks>If the specified font family does not exist in the cache, it is added along with the
|
||||
/// glyph typeface. The method ensures that the font family is inserted in a sorted order within the internal
|
||||
/// collection.</remarks>
|
||||
/// <param name="familyName">The name of the font family to which the glyph typeface belongs. Cannot be null or empty.</param>
|
||||
/// <param name="key">The key associated with the glyph typeface in the cache.</param>
|
||||
/// <param name="glyphTypeface">The glyph typeface to add to the cache. Can be null.</param>
|
||||
/// <returns><see langword="true"/> if the glyph typeface was successfully added to the cache; otherwise, <see
|
||||
/// langword="false"/>.</returns>
|
||||
protected bool TryAddGlyphTypeface(string familyName, FontCollectionKey key, IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
if (string.IsNullOrEmpty(familyName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the family already exists
|
||||
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
|
||||
{
|
||||
if (glyphTypefaces.TryGetValue(key, out var existing))
|
||||
{
|
||||
if (ReferenceEquals(existing, glyphTypeface) || (existing is null && glyphTypeface is null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return glyphTypefaces.TryAdd(key, glyphTypeface);
|
||||
}
|
||||
|
||||
// Family doesn't exist yet. Create a new dictionary instance and try to install it.
|
||||
var newDict = new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>();
|
||||
|
||||
// GetOrAdd will return the instance that ended up in the dictionary. If it's our
|
||||
// newDict instance then we won the race to add the family and should publish it.
|
||||
var dict = _glyphTypefaceCache.GetOrAdd(familyName, newDict);
|
||||
|
||||
if (ReferenceEquals(dict, newDict))
|
||||
{
|
||||
// We successfully installed the dictionary; publish the FontFamily once.
|
||||
var fontFamily = new FontFamily(Key + "#" + familyName);
|
||||
|
||||
// Add the font family to the sorted array
|
||||
AddFontFamily(fontFamily);
|
||||
}
|
||||
|
||||
// Add or compare the glyphTypeface in the resulting dictionary.
|
||||
if (dict.TryGetValue(key, out var existingAfter))
|
||||
{
|
||||
if (ReferenceEquals(existingAfter, glyphTypeface) || (existingAfter is null && glyphTypeface is null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return dict.TryAdd(key, glyphTypeface);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to locate a fallback glyph typeface with a similar font stretch to the specified key within the
|
||||
/// provided collection.
|
||||
/// </summary>
|
||||
/// <remarks>The search prioritizes font stretches closest to the requested value, expanding
|
||||
/// outward until a match is found or all options are exhausted.</remarks>
|
||||
/// <param name="glyphTypefaces">A dictionary mapping font collection keys to their corresponding glyph typefaces. Used as the source for
|
||||
/// searching fallback typefaces.</param>
|
||||
/// <param name="key">The font collection key specifying the desired font stretch and other font attributes to match.</param>
|
||||
/// <param name="glyphTypeface">When this method returns, contains the found glyph typeface with a similar stretch if one exists; otherwise,
|
||||
/// null.</param>
|
||||
/// <returns>true if a suitable fallback glyph typeface is found; otherwise, false.</returns>
|
||||
private static bool TryFindStretchFallback(
|
||||
IDictionary<FontCollectionKey, IGlyphTypeface?> glyphTypefaces,
|
||||
FontCollectionKey key,
|
||||
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
glyphTypeface = null;
|
||||
|
||||
@@ -263,9 +725,22 @@ namespace Avalonia.Media.Fonts
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryFindWeightFallback(
|
||||
ConcurrentDictionary<FontCollectionKey,
|
||||
IGlyphTypeface?> glyphTypefaces,
|
||||
/// <summary>
|
||||
/// Attempts to locate a fallback glyph typeface in the specified collection that closely matches the weight of
|
||||
/// the provided key.
|
||||
/// </summary>
|
||||
/// <remarks>The method searches for the closest available weight to the requested value,
|
||||
/// considering both lighter and heavier alternatives within the collection. If no exact match is found, it
|
||||
/// progressively searches for the nearest available weight in both directions.</remarks>
|
||||
/// <param name="glyphTypefaces">A dictionary mapping font collection keys to glyph typeface instances. The method searches this collection
|
||||
/// for a suitable fallback.</param>
|
||||
/// <param name="key">The font collection key specifying the desired font attributes, including weight, for which a fallback glyph
|
||||
/// typeface is sought.</param>
|
||||
/// <param name="glyphTypeface">When this method returns, contains the matching glyph typeface if a suitable fallback is found; otherwise,
|
||||
/// null.</param>
|
||||
/// <returns>true if a fallback glyph typeface matching the requested weight is found; otherwise, false.</returns>
|
||||
private static bool TryFindWeightFallback(
|
||||
IDictionary<FontCollectionKey, IGlyphTypeface?> glyphTypefaces,
|
||||
FontCollectionKey key,
|
||||
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
@@ -348,68 +823,22 @@ namespace Avalonia.Media.Fonts
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static Typeface GetImplicitTypeface(Typeface typeface, out string normalizedFamilyName)
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
normalizedFamilyName = typeface.FontFamily.FamilyNames.PrimaryFamilyName;
|
||||
|
||||
//Return early if no separator is present.
|
||||
if (!normalizedFamilyName.Contains(' '))
|
||||
foreach (var glyphTypefaces in _glyphTypefaceCache.Values)
|
||||
{
|
||||
return typeface;
|
||||
}
|
||||
|
||||
var style = typeface.Style;
|
||||
var weight = typeface.Weight;
|
||||
var stretch = typeface.Stretch;
|
||||
|
||||
StringBuilder? normalizedFamilyNameBuilder = null;
|
||||
var totalCharsRemoved = 0;
|
||||
|
||||
var tokenizer = new SpanStringTokenizer(normalizedFamilyName, ' ');
|
||||
|
||||
// Skip initial family name.
|
||||
tokenizer.ReadSpan();
|
||||
|
||||
while (tokenizer.TryReadSpan(out var token))
|
||||
{
|
||||
// Don't try to match numbers.
|
||||
if (new SpanStringTokenizer(token).TryReadInt32(out _))
|
||||
foreach (var pair in glyphTypefaces)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try match with font style, weight or stretch and update accordingly.
|
||||
var match = false;
|
||||
if (EnumHelper.TryParse<FontStyle>(token, true, out var newStyle))
|
||||
{
|
||||
style = newStyle;
|
||||
match = true;
|
||||
}
|
||||
else if (EnumHelper.TryParse<FontWeight>(token, true, out var newWeight))
|
||||
{
|
||||
weight = newWeight;
|
||||
match = true;
|
||||
}
|
||||
else if (EnumHelper.TryParse<FontStretch>(token, true, out var newStretch))
|
||||
{
|
||||
stretch = newStretch;
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
// Carve out matched word from the normalized name.
|
||||
normalizedFamilyNameBuilder ??= new StringBuilder(normalizedFamilyName);
|
||||
normalizedFamilyNameBuilder.Remove(tokenizer.CurrentTokenIndex - totalCharsRemoved, token.Length);
|
||||
totalCharsRemoved += token.Length;
|
||||
pair.Value?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of any trailing spaces.
|
||||
normalizedFamilyName = (normalizedFamilyNameBuilder?.ToString() ?? normalizedFamilyName).TrimEnd();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
//Preserve old font source
|
||||
return new Typeface(typeface.FontFamily, style, weight, stretch);
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Avalonia.Utilities;
|
||||
|
||||
namespace Avalonia.Media.Fonts
|
||||
{
|
||||
public static class FontFamilyLoader
|
||||
internal static class FontFamilyLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads all font assets that belong to the specified <see cref="FontFamilyKey"/>
|
||||
@@ -17,7 +17,7 @@ namespace Avalonia.Media.Fonts
|
||||
{
|
||||
if (source.IsAvares() || source.IsAbsoluteResm())
|
||||
{
|
||||
return IsFontTtfOrOtf(source) ?
|
||||
return IsFontSource(source) ?
|
||||
GetFontAssetsByExpression(source) :
|
||||
GetFontAssetsBySource(source);
|
||||
}
|
||||
@@ -25,6 +25,35 @@ namespace Avalonia.Media.Fonts
|
||||
return Enumerable.Empty<Uri>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URI refers to a font file source.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI to evaluate as a potential font file source. Must not be null.</param>
|
||||
/// <returns>true if the URI points to a recognized font file source; otherwise, false.</returns>
|
||||
public static bool IsFontSource(Uri uri)
|
||||
{
|
||||
var sourceWithoutArguments = GetSubString(uri.OriginalString, '?');
|
||||
return IsFontFile(sourceWithoutArguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified file path refers to a supported font file type.
|
||||
/// </summary>
|
||||
/// <remarks>This method performs a case-insensitive check for common font file extensions. It
|
||||
/// does not verify the existence or validity of the file at the specified path.</remarks>
|
||||
/// <param name="filePath">The path of the file to check. Can be a relative or absolute path. If null, the method returns false.</param>
|
||||
/// <returns>true if the file path ends with ".ttf", ".otf", or ".ttc" (case-insensitive); otherwise, false.</returns>
|
||||
public static bool IsFontFile(string filePath)
|
||||
{
|
||||
if (filePath is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return filePath.EndsWith(".ttf", StringComparison.OrdinalIgnoreCase)
|
||||
|| filePath.EndsWith(".otf", StringComparison.OrdinalIgnoreCase)
|
||||
|| filePath.EndsWith(".ttc", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for font assets at a given location and returns a quantity of found assets
|
||||
@@ -35,7 +64,7 @@ namespace Avalonia.Media.Fonts
|
||||
{
|
||||
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
|
||||
var availableAssets = assetLoader.GetAssets(source, null);
|
||||
return availableAssets.Where(x => IsFontTtfOrOtf(x));
|
||||
return availableAssets.Where(x => IsFontSource(x));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -97,13 +126,6 @@ namespace Avalonia.Media.Fonts
|
||||
&& path.EndsWith(fileExtension, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsFontTtfOrOtf(Uri uri)
|
||||
{
|
||||
var sourceWithoutArguments = GetSubString(uri.OriginalString, '?');
|
||||
return sourceWithoutArguments.EndsWith(".ttf", StringComparison.OrdinalIgnoreCase)
|
||||
|| sourceWithoutArguments.EndsWith(".otf", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static (string fileNameWithoutExtension, string extension) GetFileNameAndExtension(
|
||||
string path, char directorySeparator = '/')
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace Avalonia.Media.Fonts
|
||||
{
|
||||
@@ -13,12 +12,6 @@ namespace Avalonia.Media.Fonts
|
||||
/// </summary>
|
||||
Uri Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the font collection.
|
||||
/// </summary>
|
||||
/// <param name="fontManager">The font manager the collection is registered with.</param>
|
||||
void Initialize(IFontManagerImpl fontManager);
|
||||
|
||||
/// <summary>
|
||||
/// Try to get a glyph typeface for given parameters.
|
||||
/// </summary>
|
||||
@@ -70,5 +63,23 @@ namespace Avalonia.Media.Fonts
|
||||
/// <param name="syntheticGlyphTypeface"></param>
|
||||
/// <returns>Returns <c>true</c> if a synthetic glyph typface can be created; otherwise, <c>false</c></returns>
|
||||
bool TryCreateSyntheticGlyphTypeface(IGlyphTypeface glyphTypeface, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? syntheticGlyphTypeface);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the glyph typeface that most closely matches the specified font family name, style,
|
||||
/// weight, and stretch.
|
||||
/// </summary>
|
||||
/// <remarks>This method searches for a glyph typeface in the font collection cache that matches
|
||||
/// the specified parameters. If an exact match is not found, fallback mechanisms are applied to find the
|
||||
/// closest available match based on the specified style, weight, and stretch. If no suitable match is found,
|
||||
/// the method returns <see langword="false"/> and <paramref name="glyphTypeface"/> is set to <see
|
||||
/// langword="null"/>.</remarks>
|
||||
/// <param name="familyName">The name of the font family to search for. This parameter cannot be <see langword="null"/> or empty.</param>
|
||||
/// <param name="style">The desired font style.</param>
|
||||
/// <param name="weight">The desired font weight.</param>
|
||||
/// <param name="stretch">The desired font stretch.</param>
|
||||
/// <param name="glyphTypeface">When this method returns, contains the <see cref="IGlyphTypeface"/> that most closely matches the specified
|
||||
/// parameters, if a match is found; otherwise, <see langword="null"/>. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if a matching glyph typeface is found; otherwise, <see langword="false"/>.</returns>
|
||||
bool TryGetNearestMatch(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
@@ -8,45 +7,33 @@ using Avalonia.Platform;
|
||||
|
||||
namespace Avalonia.Media.Fonts
|
||||
{
|
||||
internal class SystemFontCollection : FontCollectionBase, IFontCollection2
|
||||
internal class SystemFontCollection : FontCollectionBase
|
||||
{
|
||||
private readonly FontManager _fontManager;
|
||||
private readonly List<string> _familyNames;
|
||||
private readonly IFontManagerImpl _platformImpl;
|
||||
|
||||
public SystemFontCollection(FontManager fontManager)
|
||||
public SystemFontCollection(IFontManagerImpl platformImpl)
|
||||
{
|
||||
_fontManager = fontManager;
|
||||
_familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames().Where(x => !string.IsNullOrEmpty(x)).ToList();
|
||||
_platformImpl = platformImpl ?? throw new ArgumentNullException(nameof(platformImpl));
|
||||
|
||||
var familyNames = _platformImpl.GetInstalledFontFamilyNames().Where(x => !string.IsNullOrEmpty(x));
|
||||
|
||||
foreach (var familyName in familyNames)
|
||||
{
|
||||
AddFontFamily(familyName);
|
||||
}
|
||||
}
|
||||
|
||||
public override Uri Key => FontManager.SystemFontsKey;
|
||||
|
||||
public override FontFamily this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var familyName = _familyNames[index];
|
||||
|
||||
return new FontFamily(familyName);
|
||||
}
|
||||
}
|
||||
|
||||
public override int Count => _familyNames.Count;
|
||||
|
||||
public override IEnumerator<FontFamily> GetEnumerator()
|
||||
{
|
||||
foreach (var familyName in _familyNames)
|
||||
{
|
||||
yield return new FontFamily(familyName);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
|
||||
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
glyphTypeface = null;
|
||||
var typeface = new Typeface(familyName, style, weight, stretch).Normalize(out familyName);
|
||||
|
||||
var typeface = GetImplicitTypeface(new Typeface(familyName, style, weight, stretch), out familyName);
|
||||
if (base.TryGetGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
style = typeface.Style;
|
||||
|
||||
@@ -56,102 +43,36 @@ namespace Avalonia.Media.Fonts
|
||||
|
||||
var key = new FontCollectionKey(style, weight, stretch);
|
||||
|
||||
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
|
||||
//Check cache first to avoid unnecessary calls to the font manager
|
||||
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces) && glyphTypefaces.TryGetValue(key, out glyphTypeface))
|
||||
{
|
||||
if (glyphTypefaces.TryGetValue(key, out glyphTypeface))
|
||||
{
|
||||
return glyphTypeface != null;
|
||||
}
|
||||
return glyphTypeface != null;
|
||||
}
|
||||
|
||||
glyphTypefaces ??= _glyphTypefaceCache.GetOrAdd(familyName,
|
||||
(_) => new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>());
|
||||
|
||||
//Try to create the glyph typeface via system font manager
|
||||
if (!_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch,
|
||||
out glyphTypeface))
|
||||
if (!_platformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface))
|
||||
{
|
||||
glyphTypefaces.TryAdd(key, null);
|
||||
//Add null to cache to avoid future calls
|
||||
TryAddGlyphTypeface(familyName, key, null);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var createdKey =
|
||||
new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch);
|
||||
|
||||
//No exact match
|
||||
if (createdKey != key)
|
||||
//Add to cache
|
||||
if (!TryAddGlyphTypeface(glyphTypeface))
|
||||
{
|
||||
//Add the created glyph typeface to the cache so we can match it.
|
||||
glyphTypefaces.TryAdd(createdKey, glyphTypeface);
|
||||
|
||||
//Try to find nearest match if possible
|
||||
if (TryGetNearestMatch(glyphTypefaces, key, out var nearestMatch))
|
||||
{
|
||||
glyphTypeface = nearestMatch;
|
||||
}
|
||||
|
||||
//Try to create a synthetic glyph typeface
|
||||
if (TryCreateSyntheticGlyphTypeface(glyphTypeface, style, weight, stretch, out var syntheticGlyphTypeface))
|
||||
{
|
||||
glyphTypeface = syntheticGlyphTypeface;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
glyphTypefaces.TryAdd(key, glyphTypeface);
|
||||
|
||||
return glyphTypeface != null;
|
||||
//Requested glyph typeface should be in cache now
|
||||
return base.TryGetGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface);
|
||||
}
|
||||
|
||||
public override void Initialize(IFontManagerImpl fontManager)
|
||||
{
|
||||
//We initialize the system font collection during construction.
|
||||
}
|
||||
|
||||
public void AddCustomFontSource(Uri source)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoadGlyphTypefaces(_fontManager.PlatformImpl, source);
|
||||
}
|
||||
|
||||
private void LoadGlyphTypefaces(IFontManagerImpl fontManager, Uri source)
|
||||
{
|
||||
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
|
||||
|
||||
var fontAssets = FontFamilyLoader.LoadFontAssets(source);
|
||||
|
||||
foreach (var fontAsset in fontAssets)
|
||||
{
|
||||
var stream = assetLoader.Open(fontAsset);
|
||||
|
||||
if (!fontManager.TryCreateGlyphTypeface(stream, FontSimulations.None, out var glyphTypeface))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add TypographicFamilyName to the cache
|
||||
if (glyphTypeface is IGlyphTypeface2 glyphTypeface2 && !string.IsNullOrEmpty(glyphTypeface2.TypographicFamilyName))
|
||||
{
|
||||
AddGlyphTypefaceByFamilyName(glyphTypeface2.TypographicFamilyName, glyphTypeface);
|
||||
}
|
||||
|
||||
AddGlyphTypefaceByFamilyName(glyphTypeface.FamilyName, glyphTypeface);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
|
||||
public override bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList<Typeface>? familyTypefaces)
|
||||
{
|
||||
familyTypefaces = null;
|
||||
|
||||
if (_fontManager.PlatformImpl is IFontManagerImpl2 fontManagerImpl2)
|
||||
if (_platformImpl is IFontManagerImpl2 fontManagerImpl2)
|
||||
{
|
||||
return fontManagerImpl2.TryGetFamilyTypefaces(familyName, out familyTypefaces);
|
||||
}
|
||||
@@ -160,19 +81,30 @@ namespace Avalonia.Media.Fonts
|
||||
}
|
||||
|
||||
public override bool TryMatchCharacter(int codepoint, FontStyle style, FontWeight weight, FontStretch stretch, string? familyName,
|
||||
CultureInfo? culture, out Typeface match)
|
||||
CultureInfo? culture, out Typeface match)
|
||||
{
|
||||
var requestedKey = new FontCollectionKey { Style = style, Weight = weight, Stretch = stretch };
|
||||
|
||||
//TODO12: Think about removing familyName parameter
|
||||
match = default;
|
||||
|
||||
if (_fontManager.PlatformImpl is IFontManagerImpl2 fontManagerImpl2)
|
||||
if (base.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out match))
|
||||
{
|
||||
if (fontManagerImpl2.TryMatchCharacter(codepoint, style, weight, stretch, culture, out var glyphTypeface))
|
||||
{
|
||||
AddGlyphTypefaceByFamilyName(glyphTypeface.FamilyName, glyphTypeface);
|
||||
var matchKey = new FontCollectionKey { Style = match.Style, Weight = match.Weight, Stretch = match.Stretch };
|
||||
|
||||
if (requestedKey == matchKey)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_platformImpl is IFontManagerImpl2 fontManagerImpl2)
|
||||
{
|
||||
if (fontManagerImpl2.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out var glyphTypeface))
|
||||
{
|
||||
match = new Typeface(glyphTypeface.FamilyName, glyphTypeface.Style, glyphTypeface.Weight,
|
||||
glyphTypeface.Stretch);
|
||||
glyphTypeface.Stretch);
|
||||
|
||||
// Add to cache if not already present
|
||||
TryAddGlyphTypeface(glyphTypeface);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -181,26 +113,8 @@ namespace Avalonia.Media.Fonts
|
||||
}
|
||||
else
|
||||
{
|
||||
return _fontManager.PlatformImpl.TryMatchCharacter(codepoint, style, weight, stretch, culture, out match);
|
||||
return _platformImpl.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out match);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddGlyphTypefaceByFamilyName(string familyName, IGlyphTypeface glyphTypeface)
|
||||
{
|
||||
// Add family name to the collection if not exists
|
||||
if (!_familyNames.Contains(familyName))
|
||||
{
|
||||
_familyNames.Add(familyName);
|
||||
}
|
||||
|
||||
// Get or create the typefaces dictionary for the family name
|
||||
if (!_glyphTypefaceCache.TryGetValue(familyName, out var typefaces))
|
||||
{
|
||||
_glyphTypefaceCache[familyName] = typefaces = new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>();
|
||||
}
|
||||
|
||||
// Add the glyph typeface to the cache
|
||||
typefaces.TryAdd(new FontCollectionKey(glyphTypeface.Style, glyphTypeface.Weight, glyphTypeface.Stretch), glyphTypeface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Avalonia.Utilities;
|
||||
|
||||
namespace Avalonia.Media
|
||||
{
|
||||
@@ -127,5 +129,81 @@ namespace Avalonia.Media
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the typeface by extracting and removing style, weight, and stretch information from the font
|
||||
/// family name, and returns a new <see cref="Typeface"/> instance with the updated properties.
|
||||
/// </summary>
|
||||
/// <remarks>This method analyzes the font family name to identify and extract any style, weight,
|
||||
/// or stretch information embedded within it. If such information is found, it is removed from the family name,
|
||||
/// and the corresponding properties of the returned <see cref="Typeface"/> are updated accordingly. If no such
|
||||
/// information is found, the method returns the current instance without modification.</remarks>
|
||||
/// <param name="normalizedFamilyName">When this method returns, contains the normalized font family name with style, weight, and stretch
|
||||
/// information removed. This parameter is passed uninitialized.</param>
|
||||
/// <returns>A new <see cref="Typeface"/> instance with the updated <see cref="FontStyle"/>, <see cref="FontWeight"/>,
|
||||
/// and <see cref="FontStretch"/> properties, or the current instance if no normalization was performed.</returns>
|
||||
public Typeface Normalize(out string normalizedFamilyName)
|
||||
{
|
||||
normalizedFamilyName = FontFamily.FamilyNames.PrimaryFamilyName;
|
||||
|
||||
//Return early if no separator is present.
|
||||
if (!normalizedFamilyName.Contains(' '))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var style = Style;
|
||||
var weight = Weight;
|
||||
var stretch = Stretch;
|
||||
|
||||
StringBuilder? normalizedFamilyNameBuilder = null;
|
||||
var totalCharsRemoved = 0;
|
||||
|
||||
var tokenizer = new SpanStringTokenizer(normalizedFamilyName, ' ');
|
||||
|
||||
// Skip initial family name.
|
||||
tokenizer.ReadSpan();
|
||||
|
||||
while (tokenizer.TryReadSpan(out var token))
|
||||
{
|
||||
// Don't try to match numbers.
|
||||
if (new SpanStringTokenizer(token).TryReadInt32(out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try match with font style, weight or stretch and update accordingly.
|
||||
var match = false;
|
||||
if (EnumHelper.TryParse<FontStyle>(token, true, out var newStyle))
|
||||
{
|
||||
style = newStyle;
|
||||
match = true;
|
||||
}
|
||||
else if (EnumHelper.TryParse<FontWeight>(token, true, out var newWeight))
|
||||
{
|
||||
weight = newWeight;
|
||||
match = true;
|
||||
}
|
||||
else if (EnumHelper.TryParse<FontStretch>(token, true, out var newStretch))
|
||||
{
|
||||
stretch = newStretch;
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
// Carve out matched word from the normalized name.
|
||||
normalizedFamilyNameBuilder ??= new StringBuilder(normalizedFamilyName);
|
||||
normalizedFamilyNameBuilder.Remove(tokenizer.CurrentTokenIndex - totalCharsRemoved, token.Length);
|
||||
totalCharsRemoved += token.Length;
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of any trailing spaces.
|
||||
normalizedFamilyName = (normalizedFamilyNameBuilder?.ToString() ?? normalizedFamilyName).TrimEnd();
|
||||
|
||||
//Preserve old font source
|
||||
return new Typeface(FontFamily, style, weight, stretch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Fonts;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
namespace Avalonia.Platform
|
||||
@@ -29,13 +28,14 @@ namespace Avalonia.Platform
|
||||
/// <param name="fontStyle">The font style.</param>
|
||||
/// <param name="fontWeight">The font weight.</param>
|
||||
/// <param name="fontStretch">The font stretch.</param>
|
||||
/// <param name="familyName">The family name. This is optional and can be used as an initial hint for matching.</param>
|
||||
/// <param name="culture">The culture.</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, FontStretch fontStretch, CultureInfo? culture, out Typeface typeface);
|
||||
FontWeight fontWeight, FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface typeface);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a glyph typeface for specified parameters.
|
||||
@@ -72,13 +72,14 @@ namespace Avalonia.Platform
|
||||
/// <param name="fontStyle">The font style.</param>
|
||||
/// <param name="fontWeight">The font weight.</param>
|
||||
/// <param name="fontStretch">The font stretch.</param>
|
||||
/// <param name="familyName">The family name. This is optional and can be used as an initial hint for matching.</param>
|
||||
/// <param name="culture">The culture.</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, FontStretch fontStretch, CultureInfo? culture, [NotNullWhen(true)] out IGlyphTypeface? typeface);
|
||||
FontWeight fontWeight, FontStretch fontStretch, string? familyName, CultureInfo? culture, [NotNullWhen(true)] out IGlyphTypeface? typeface);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a list of typefaces for the specified family name.
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Avalonia
|
||||
{
|
||||
if(fontManager.SystemFonts is SystemFontCollection systemFontCollection)
|
||||
{
|
||||
systemFontCollection.AddCustomFontSource(fontSource);
|
||||
systemFontCollection.TryAddFontSource(fontSource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -216,8 +216,7 @@ namespace Avalonia.Headless
|
||||
}
|
||||
|
||||
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
|
||||
FontStretch fontStretch,
|
||||
CultureInfo? culture, out Typeface fontKey)
|
||||
FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface fontKey)
|
||||
{
|
||||
fontKey = new Typeface(_defaultFamilyName);
|
||||
|
||||
@@ -281,8 +280,7 @@ namespace Avalonia.Headless
|
||||
}
|
||||
|
||||
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
|
||||
FontStretch fontStretch,
|
||||
CultureInfo? culture, out Typeface fontKey)
|
||||
FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface fontKey)
|
||||
{
|
||||
fontKey = new Typeface(_defaultFamilyName);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform;
|
||||
using SkiaSharp;
|
||||
@@ -35,9 +34,9 @@ namespace Avalonia.Skia
|
||||
[ThreadStatic] private static string[]? t_languageTagBuffer;
|
||||
|
||||
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
|
||||
FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface fontKey)
|
||||
FontWeight fontWeight, FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface fontKey)
|
||||
{
|
||||
if (!TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, culture, out SKTypeface? skTypeface))
|
||||
if (!TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, familyName, culture, out SKTypeface? skTypeface))
|
||||
{
|
||||
fontKey = default;
|
||||
|
||||
@@ -61,10 +60,11 @@ namespace Avalonia.Skia
|
||||
FontStyle fontStyle,
|
||||
FontWeight fontWeight,
|
||||
FontStretch fontStretch,
|
||||
string? familyName,
|
||||
CultureInfo? culture,
|
||||
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
|
||||
{
|
||||
if (!TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, culture, out SKTypeface? skTypeface))
|
||||
if (!TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, familyName, culture, out SKTypeface? skTypeface))
|
||||
{
|
||||
glyphTypeface = null;
|
||||
|
||||
@@ -81,6 +81,7 @@ namespace Avalonia.Skia
|
||||
FontStyle fontStyle,
|
||||
FontWeight fontWeight,
|
||||
FontStretch fontStretch,
|
||||
string? familyName,
|
||||
CultureInfo? culture,
|
||||
[NotNullWhen(true)] out SKTypeface? skTypeface)
|
||||
{
|
||||
@@ -110,7 +111,7 @@ namespace Avalonia.Skia
|
||||
t_languageTagBuffer ??= new string[1];
|
||||
t_languageTagBuffer[0] = culture.Name;
|
||||
|
||||
skTypeface = _skFontManager.MatchCharacter(null, skFontStyle, t_languageTagBuffer, codepoint);
|
||||
skTypeface = _skFontManager.MatchCharacter(string.IsNullOrEmpty(familyName) ? null : familyName, skFontStyle, t_languageTagBuffer, codepoint);
|
||||
|
||||
return skTypeface != null;
|
||||
}
|
||||
|
||||
@@ -120,30 +120,22 @@ namespace Avalonia.Skia
|
||||
|
||||
foreach (var nameRecord in _nameTable)
|
||||
{
|
||||
if(nameRecord.NameID == KnownNameIds.FontFamilyName)
|
||||
var languageId = nameRecord.LanguageID == 0 ?
|
||||
(ushort)CultureInfo.InvariantCulture.LCID :
|
||||
nameRecord.LanguageID;
|
||||
|
||||
switch (nameRecord.NameID)
|
||||
{
|
||||
if (nameRecord.Platform != PlatformIDs.Windows || nameRecord.LanguageID == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!familyNames.ContainsKey(nameRecord.LanguageID))
|
||||
{
|
||||
familyNames[nameRecord.LanguageID] = nameRecord.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if(nameRecord.NameID == KnownNameIds.FontSubfamilyName)
|
||||
{
|
||||
if (nameRecord.Platform != PlatformIDs.Windows || nameRecord.LanguageID == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!faceNames.ContainsKey(nameRecord.LanguageID))
|
||||
{
|
||||
faceNames[nameRecord.LanguageID] = nameRecord.Value;
|
||||
}
|
||||
case KnownNameIds.FontFamilyName:
|
||||
{
|
||||
familyNames.TryAdd(languageId, nameRecord.Value);
|
||||
break;
|
||||
}
|
||||
case KnownNameIds.FontSubfamilyName:
|
||||
{
|
||||
faceNames.TryAdd(languageId, nameRecord.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,5 +23,24 @@ namespace Avalonia.Base.UnitTests.Media
|
||||
{
|
||||
Assert.Equal(new Typeface("Font A").GetHashCode(), new Typeface("Font A").GetHashCode());
|
||||
}
|
||||
|
||||
[InlineData("Hello World 6", "Hello World 6", FontStyle.Normal, FontWeight.Normal)]
|
||||
[InlineData("Hello World Italic", "Hello World", FontStyle.Italic, FontWeight.Normal)]
|
||||
[InlineData("Hello World Italic Bold", "Hello World", FontStyle.Italic, FontWeight.Bold)]
|
||||
[InlineData("FontAwesome 6 Free Regular", "FontAwesome 6 Free", FontStyle.Normal, FontWeight.Normal)]
|
||||
[InlineData("FontAwesome 6 Free Solid", "FontAwesome 6 Free", FontStyle.Normal, FontWeight.Solid)]
|
||||
[InlineData("FontAwesome 6 Brands", "FontAwesome 6 Brands", FontStyle.Normal, FontWeight.Normal)]
|
||||
[Theory]
|
||||
public void Should_Get_Implicit_Typeface(string input, string familyName, FontStyle style, FontWeight weight)
|
||||
{
|
||||
var typeface = new Typeface(input);
|
||||
|
||||
var normalizedTypeface = typeface.Normalize(out var normalizedFamilyName);
|
||||
|
||||
Assert.Equal(familyName, normalizedFamilyName);
|
||||
Assert.Equal(style, normalizedTypeface.Style);
|
||||
Assert.Equal(weight, normalizedTypeface.Weight);
|
||||
Assert.Equal(FontStretch.Normal, normalizedTypeface.Stretch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using Avalonia.Layout;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Markup.Xaml.Templates;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.UnitTests;
|
||||
using Avalonia.VisualTree;
|
||||
@@ -1238,7 +1239,8 @@ namespace Avalonia.Controls.UnitTests
|
||||
keyboardNavigation: () => new KeyboardNavigationHandler(),
|
||||
inputManager: new InputManager(),
|
||||
renderInterface: new HeadlessPlatformRenderInterface(),
|
||||
textShaperImpl: new HeadlessTextShaperStub()));
|
||||
textShaperImpl: new HeadlessTextShaperStub(),
|
||||
assetLoader: new StandardAssetLoader()));
|
||||
}
|
||||
|
||||
private class ItemsControlWithContainer : ItemsControl
|
||||
|
||||
@@ -980,7 +980,7 @@ namespace Avalonia.Controls.UnitTests
|
||||
private static IDisposable Start(TestServices services = null)
|
||||
{
|
||||
CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
|
||||
return UnitTestApplication.Start(services ?? Services);
|
||||
return UnitTestApplication.Start((services ?? Services).With(assetLoader: new StandardAssetLoader()));
|
||||
}
|
||||
|
||||
private class Class1 : NotifyingBase
|
||||
|
||||
@@ -13,6 +13,7 @@ using Avalonia.Data;
|
||||
using Avalonia.Headless;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.UnitTests;
|
||||
using Avalonia.VisualTree;
|
||||
@@ -1351,7 +1352,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
|
||||
keyboardNavigation: () => new KeyboardNavigationHandler(),
|
||||
inputManager: new InputManager(),
|
||||
renderInterface: new HeadlessPlatformRenderInterface(),
|
||||
textShaperImpl: new HeadlessTextShaperStub()));
|
||||
textShaperImpl: new HeadlessTextShaperStub(),
|
||||
assetLoader: new StandardAssetLoader()));
|
||||
}
|
||||
|
||||
private class TestSelector : SelectingItemsControl
|
||||
|
||||
@@ -749,7 +749,7 @@ namespace Avalonia.Controls.UnitTests
|
||||
[Fact]
|
||||
public void TextBox_CaretIndex_Persists_When_Focus_Lost()
|
||||
{
|
||||
using (UnitTestApplication.Start(FocusServices))
|
||||
using (UnitTestApplication.Start(FocusServices.With(assetLoader: new StandardAssetLoader())))
|
||||
{
|
||||
var target1 = new TextBox
|
||||
{
|
||||
@@ -2160,7 +2160,8 @@ namespace Avalonia.Controls.UnitTests
|
||||
standardCursorFactory: Mock.Of<ICursorFactory>(),
|
||||
renderInterface: new HeadlessPlatformRenderInterface(),
|
||||
textShaperImpl: new HeadlessTextShaperStub(),
|
||||
fontManagerImpl: new HeadlessFontManagerStub());
|
||||
fontManagerImpl: new HeadlessFontManagerStub(),
|
||||
assetLoader: new StandardAssetLoader());
|
||||
|
||||
internal static IControlTemplate CreateTemplate()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Headless;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.UnitTests;
|
||||
using Avalonia.VisualTree;
|
||||
using Xunit;
|
||||
@@ -325,7 +326,8 @@ namespace Avalonia.Controls.UnitTests
|
||||
TestServices.MockThreadingInterface.With(
|
||||
fontManagerImpl: new HeadlessFontManagerStub(),
|
||||
renderInterface: new HeadlessPlatformRenderInterface(),
|
||||
textShaperImpl: new HeadlessTextShaperStub()));
|
||||
textShaperImpl: new HeadlessTextShaperStub(),
|
||||
assetLoader: new StandardAssetLoader()));
|
||||
}
|
||||
|
||||
private static (TransitioningContentControl, TestTransition) CreateTarget(object content)
|
||||
|
||||
@@ -15,6 +15,7 @@ using Avalonia.Input.Platform;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Markup.Xaml.Templates;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.UnitTests;
|
||||
using Avalonia.VisualTree;
|
||||
@@ -1841,7 +1842,8 @@ namespace Avalonia.Controls.UnitTests
|
||||
keyboardNavigation: () => new KeyboardNavigationHandler(),
|
||||
inputManager: new InputManager(),
|
||||
renderInterface: new HeadlessPlatformRenderInterface(),
|
||||
textShaperImpl: new HeadlessTextShaperStub()));
|
||||
textShaperImpl: new HeadlessTextShaperStub(),
|
||||
assetLoader: new StandardAssetLoader()));
|
||||
}
|
||||
|
||||
private class Node : NotifyingBase
|
||||
|
||||
@@ -20,4 +20,9 @@
|
||||
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
|
||||
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="..\Avalonia.RenderTests\Assets\Inter-Regular.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Fonts;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.UnitTests;
|
||||
using Xunit;
|
||||
|
||||
namespace Avalonia.Skia.UnitTests.Media
|
||||
{
|
||||
public class CustomFontCollectionTests
|
||||
{
|
||||
private const string NotoMono =
|
||||
"resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests";
|
||||
|
||||
[Fact]
|
||||
public void Should_AddGlyphTypeface_By_Stream()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
||||
{
|
||||
var fontManager = FontManager.Current;
|
||||
|
||||
var fontCollection = new CustomFontCollection(new Uri("fonts:custom", UriKind.Absolute));
|
||||
|
||||
fontManager.AddFontCollection(fontCollection);
|
||||
|
||||
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
|
||||
|
||||
var assets = assetLoader.GetAssets(new Uri(NotoMono, UriKind.Absolute), null).ToArray();
|
||||
|
||||
Assert.NotEmpty(assets);
|
||||
|
||||
var notoMonoLocation = assets.First();
|
||||
|
||||
using var notoMonoStream = assetLoader.Open(notoMonoLocation);
|
||||
|
||||
Assert.NotNull(notoMonoStream);
|
||||
|
||||
Assert.True(fontCollection.TryAddGlyphTypeface(notoMonoStream, out var glyphTypeface));
|
||||
|
||||
Assert.Equal("Inter", glyphTypeface.FamilyName);
|
||||
|
||||
Assert.True(fontManager.TryGetGlyphTypeface(new Typeface("fonts:custom#Inter"), out var secondGlyphTypeface));
|
||||
|
||||
Assert.Equal(glyphTypeface, secondGlyphTypeface);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Enumerate_FontFamilies()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
||||
{
|
||||
var fontManager = FontManager.Current;
|
||||
|
||||
var fontCollection = new CustomFontCollection(new Uri("fonts:custom", UriKind.Absolute));
|
||||
|
||||
fontManager.AddFontCollection(fontCollection);
|
||||
|
||||
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
|
||||
|
||||
var assets = assetLoader.GetAssets(new Uri(NotoMono, UriKind.Absolute), null).Where(x => x.AbsolutePath.EndsWith(".ttf")).ToArray();
|
||||
|
||||
foreach (var asset in assets)
|
||||
{
|
||||
fontCollection.TryAddGlyphTypeface(assetLoader.Open(asset), out _);
|
||||
}
|
||||
|
||||
var families = fontCollection.ToArray();
|
||||
|
||||
Assert.True(families.Length >= assets.Length);
|
||||
|
||||
var other = new CustomFontCollection(new Uri("fonts:other", UriKind.Absolute));
|
||||
|
||||
foreach (var family in families)
|
||||
{
|
||||
var familyTypefaces = family.FamilyTypefaces;
|
||||
|
||||
foreach (var typeface in familyTypefaces)
|
||||
{
|
||||
other.TryAddGlyphTypeface(typeface.GlyphTypeface);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(families.Length, other.Count);
|
||||
|
||||
for (int i = 0; i < families.Length; i++)
|
||||
{
|
||||
Assert.Equal(families[i].Name, other[i].Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_AddFontSource_From_File()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
||||
{
|
||||
var fontManager = FontManager.Current;
|
||||
var fontCollection = new CustomFontCollection(new Uri("fonts:custom", UriKind.Absolute));
|
||||
fontManager.AddFontCollection(fontCollection);
|
||||
|
||||
// Path to the test font
|
||||
var fontPath = Path.Combine(AppContext.BaseDirectory, "Assets", "Inter-Regular.ttf");
|
||||
Assert.True(File.Exists(fontPath));
|
||||
|
||||
var fontUri = new Uri(fontPath, UriKind.Absolute);
|
||||
|
||||
// Add the font file
|
||||
Assert.True(fontCollection.TryAddFontSource(fontUri));
|
||||
|
||||
// Check if the font was loaded
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("Inter", FontStyle.Normal, FontWeight.Regular, FontStretch.Normal, out var glyphTypeface));
|
||||
Assert.Equal("Inter", glyphTypeface.FamilyName);
|
||||
|
||||
// Check if the FontManager can find the font
|
||||
Assert.True(fontManager.TryGetGlyphTypeface(new Typeface("fonts:custom#Inter"), out var glyphTypeface2));
|
||||
Assert.Equal(glyphTypeface, glyphTypeface2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_AddFontSource_From_Folder()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
||||
{
|
||||
var fontManager = FontManager.Current;
|
||||
var fontCollection = new CustomFontCollection(new Uri("fonts:custom", UriKind.Absolute));
|
||||
fontManager.AddFontCollection(fontCollection);
|
||||
|
||||
// Path to the test fonts
|
||||
var fontsFolder = Path.Combine(AppContext.BaseDirectory, "Assets");
|
||||
Assert.True(Directory.Exists(fontsFolder));
|
||||
|
||||
var folderUri = new Uri(fontsFolder + Path.DirectorySeparatorChar, UriKind.Absolute);
|
||||
|
||||
// Add the fonts
|
||||
Assert.True(fontCollection.TryAddFontSource(folderUri));
|
||||
|
||||
// Check if the font was loaded
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("Inter", FontStyle.Normal, FontWeight.Regular, FontStretch.Normal, out var glyphTypeface));
|
||||
Assert.Equal("Inter", glyphTypeface.FamilyName);
|
||||
|
||||
// Check if the FontManager can find the font
|
||||
Assert.True(fontManager.TryGetGlyphTypeface(new Typeface("fonts:custom#Inter"), out var glyphTypeface2));
|
||||
Assert.Equal(glyphTypeface, glyphTypeface2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_AddFontSource_From_Resource()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
||||
{
|
||||
var fontManager = FontManager.Current;
|
||||
var fontCollection = new CustomFontCollection(new Uri("fonts:custom", UriKind.Absolute));
|
||||
fontManager.AddFontCollection(fontCollection);
|
||||
|
||||
// Use the NotoMono resource as FontSource
|
||||
var notoMonoUri = new Uri(NotoMono, UriKind.Absolute);
|
||||
|
||||
// Add the font resource
|
||||
Assert.True(fontCollection.TryAddFontSource(notoMonoUri));
|
||||
|
||||
// Get the loaded family names
|
||||
var families = fontCollection.ToArray();
|
||||
|
||||
Assert.NotEmpty(families);
|
||||
|
||||
// Try to get a GlyphTypeface
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("Noto Mono", FontStyle.Normal, FontWeight.Regular, FontStretch.Normal, out var glyphTypeface));
|
||||
Assert.Equal("Noto Mono", glyphTypeface.FamilyName);
|
||||
|
||||
// Check if the FontManager can find the font
|
||||
Assert.True(fontManager.TryGetGlyphTypeface(new Typeface("fonts:custom#Noto Mono"), out var glyphTypeface2));
|
||||
Assert.Equal(glyphTypeface, glyphTypeface2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class CustomFontCollection(Uri key) : FontCollectionBase
|
||||
{
|
||||
public override Uri Key { get; } = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Media;
|
||||
@@ -13,18 +14,28 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
public class CustomFontManagerImpl : IFontManagerImpl, IDisposable
|
||||
{
|
||||
private readonly string _defaultFamilyName;
|
||||
private readonly IFontCollection _customFonts;
|
||||
private bool _isInitialized;
|
||||
private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
|
||||
private IFontCollection? _systemFonts;
|
||||
|
||||
public CustomFontManagerImpl()
|
||||
{
|
||||
var source = new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests");
|
||||
|
||||
_defaultFamilyName = source.AbsoluteUri + "#Noto Mono";
|
||||
|
||||
_customFonts = new EmbeddedFontCollection(source, source);
|
||||
_defaultFamilyName = FontManager.SystemFontsKey + "#Noto Mono";
|
||||
}
|
||||
|
||||
public IFontCollection SystemFonts
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_systemFonts is null)
|
||||
{
|
||||
var source = new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests");
|
||||
|
||||
_systemFonts = new EmbeddedFontCollection(FontManager.SystemFontsKey, source);
|
||||
}
|
||||
|
||||
return _systemFonts;
|
||||
}
|
||||
}
|
||||
public string GetDefaultFontFamilyName()
|
||||
{
|
||||
return _defaultFamilyName;
|
||||
@@ -32,27 +43,46 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
|
||||
public string[] GetInstalledFontFamilyNames(bool checkForUpdates = false)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
// Directly load from assets to avoid creating the full font collection
|
||||
try
|
||||
{
|
||||
_customFonts.Initialize(this);
|
||||
var key = new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests");
|
||||
|
||||
_isInitialized = true;
|
||||
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
|
||||
|
||||
var fontAssets = FontFamilyLoader.LoadFontAssets(key);
|
||||
var names = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
foreach (var fontAsset in fontAssets)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = assetLoader.Open(fontAsset);
|
||||
using var sk = SKTypeface.FromStream(stream);
|
||||
|
||||
if (sk != null && !string.IsNullOrEmpty(sk.FamilyName))
|
||||
{
|
||||
names.Add(sk.FamilyName);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore faulty assets
|
||||
}
|
||||
}
|
||||
|
||||
return names.ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return _customFonts.Select(x=> x.Name).ToArray();
|
||||
}
|
||||
|
||||
private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
|
||||
|
||||
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch,
|
||||
CultureInfo culture, out Typeface typeface)
|
||||
string? familyName, CultureInfo? culture, out Typeface typeface)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
_customFonts.Initialize(this);
|
||||
}
|
||||
|
||||
if(_customFonts.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, null, culture, out typeface))
|
||||
if(SystemFonts.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, familyName, culture, out typeface))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -68,12 +98,7 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
|
||||
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
_customFonts.Initialize(this);
|
||||
}
|
||||
|
||||
if (_customFonts.TryGetGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface))
|
||||
if (SystemFonts.TryGetGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -97,7 +122,7 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_customFonts.Dispose();
|
||||
_systemFonts?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,8 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
{
|
||||
public class EmbeddedFontCollectionTests
|
||||
{
|
||||
private const string s_notoMono =
|
||||
"resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono";
|
||||
|
||||
private const string s_manrope = "resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#Manrope";
|
||||
|
||||
private const string s_misans = "resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#MiSans";
|
||||
|
||||
private const string s_fontAssets =
|
||||
"resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests";
|
||||
|
||||
[InlineData(FontWeight.SemiLight, FontStyle.Normal)]
|
||||
[InlineData(FontWeight.Bold, FontStyle.Italic)]
|
||||
@@ -28,14 +23,13 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
[Theory]
|
||||
public void Should_Get_Near_Matching_Typeface(FontWeight fontWeight, FontStyle fontStyle)
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl())))
|
||||
{
|
||||
var source = new Uri(s_notoMono, UriKind.Absolute);
|
||||
var key = new Uri("fonts:testFonts", UriKind.Absolute);
|
||||
var source = new Uri(s_fontAssets, UriKind.Absolute);
|
||||
|
||||
var fontCollection = new TestEmbeddedFontCollection(source, source);
|
||||
|
||||
fontCollection.Initialize(new CustomFontManagerImpl());
|
||||
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("Noto Mono", fontStyle, fontWeight, FontStretch.Normal, out var glyphTypeface));
|
||||
|
||||
var actual = glyphTypeface.FamilyName;
|
||||
@@ -47,13 +41,12 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
[Fact]
|
||||
public void Should_Not_Get_Typeface_For_Invalid_FamilyName()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl())))
|
||||
{
|
||||
var source = new Uri(s_notoMono, UriKind.Absolute);
|
||||
var key = new Uri("fonts:testFonts", UriKind.Absolute);
|
||||
var source = new Uri(s_fontAssets, UriKind.Absolute);
|
||||
|
||||
var fontCollection = new TestEmbeddedFontCollection(source, source);
|
||||
|
||||
fontCollection.Initialize(new CustomFontManagerImpl());
|
||||
var fontCollection = new TestEmbeddedFontCollection(key, source);
|
||||
|
||||
Assert.False(fontCollection.TryGetGlyphTypeface("ABC", FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, out _));
|
||||
}
|
||||
@@ -62,13 +55,12 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
[Fact]
|
||||
public void Should_Get_Typeface_For_Partial_FamilyName()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl())))
|
||||
{
|
||||
var source = new Uri("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#T", UriKind.Absolute);
|
||||
var key = new Uri("fonts:testFonts", UriKind.Absolute);
|
||||
var source = new Uri(s_fontAssets, UriKind.Absolute);
|
||||
|
||||
var fontCollection = new TestEmbeddedFontCollection(source, source);
|
||||
|
||||
fontCollection.Initialize(new CustomFontManagerImpl());
|
||||
var fontCollection = new TestEmbeddedFontCollection(key, source);
|
||||
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("T", FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, out var glyphTypeface));
|
||||
|
||||
@@ -79,13 +71,12 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
[Fact]
|
||||
public void Should_Get_Typeface_For_TypographicFamilyName()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl())))
|
||||
{
|
||||
var source = new Uri(s_manrope, UriKind.Absolute);
|
||||
var key = new Uri("fonts:testFonts", UriKind.Absolute);
|
||||
var source = new Uri(s_fontAssets, UriKind.Absolute);
|
||||
|
||||
var fontCollection = new TestEmbeddedFontCollection(source, source);
|
||||
|
||||
fontCollection.Initialize(new CustomFontManagerImpl());
|
||||
var fontCollection = new TestEmbeddedFontCollection(key, source);
|
||||
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("Manrope", FontStyle.Normal, FontWeight.Light, FontStretch.Normal, out var glyphTypeface));
|
||||
|
||||
@@ -102,13 +93,12 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
[Fact]
|
||||
public void Should_Cache_Synthetic_GlyphTypeface()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new CustomFontManagerImpl())))
|
||||
{
|
||||
var source = new Uri(s_manrope, UriKind.Absolute);
|
||||
var key = new Uri("fonts:testFonts", UriKind.Absolute);
|
||||
var source = new Uri(s_fontAssets, UriKind.Absolute);
|
||||
|
||||
var fontCollection = new TestEmbeddedFontCollection(source, source, true);
|
||||
|
||||
fontCollection.Initialize(new CustomFontManagerImpl());
|
||||
var fontCollection = new TestEmbeddedFontCollection(key, source, true);
|
||||
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("Manrope", FontStyle.Normal, FontWeight.ExtraBlack, FontStretch.Normal, out var glyphTypeface));
|
||||
|
||||
@@ -125,18 +115,19 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
[Fact]
|
||||
public void Should_Cache_Nearest_Match_For_MiSans()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
||||
{
|
||||
var source = new Uri(s_misans, UriKind.Absolute);
|
||||
var source = new Uri(s_fontAssets, UriKind.Absolute);
|
||||
|
||||
var fontCollection = new TestEmbeddedFontCollection(source, source);
|
||||
|
||||
fontCollection.Initialize(new CustomFontManagerImpl());
|
||||
|
||||
// Font weight 304
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("MiSans", FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, out var regularGlyphTypeface));
|
||||
|
||||
// Font weight regular (400)
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("MiSans", FontStyle.Normal, FontWeight.Bold, FontStretch.Normal, out var boldGlyphTypeface));
|
||||
|
||||
// Font weight 700
|
||||
Assert.True(fontCollection.GlyphTypefaceCache.TryGetValue("MiSans", out var glyphTypefaces));
|
||||
|
||||
Assert.Equal(3, glyphTypefaces.Count);
|
||||
|
||||
@@ -18,33 +18,12 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
private const string NotoMono =
|
||||
"resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests";
|
||||
|
||||
[InlineData("Hello World 6", "Hello World 6", FontStyle.Normal, FontWeight.Normal)]
|
||||
[InlineData("Hello World Italic", "Hello World", FontStyle.Italic, FontWeight.Normal)]
|
||||
[InlineData("Hello World Italic Bold", "Hello World", FontStyle.Italic, FontWeight.Bold)]
|
||||
[InlineData("FontAwesome 6 Free Regular", "FontAwesome 6 Free", FontStyle.Normal, FontWeight.Normal)]
|
||||
[InlineData("FontAwesome 6 Free Solid", "FontAwesome 6 Free", FontStyle.Normal, FontWeight.Solid)]
|
||||
[InlineData("FontAwesome 6 Brands", "FontAwesome 6 Brands", FontStyle.Normal, FontWeight.Normal)]
|
||||
[Theory]
|
||||
public void Should_Get_Implicit_Typeface(string input, string familyName, FontStyle style, FontWeight weight)
|
||||
{
|
||||
var typeface = new Typeface(input);
|
||||
|
||||
var result = FontCollectionBase.GetImplicitTypeface(typeface, out var normalizedFamilyName);
|
||||
|
||||
Assert.Equal(familyName, normalizedFamilyName);
|
||||
Assert.Equal(style, result.Style);
|
||||
Assert.Equal(weight, result.Weight);
|
||||
Assert.Equal(FontStretch.Normal, result.Stretch);
|
||||
}
|
||||
|
||||
[Win32Fact("Relies on some installed font family")]
|
||||
public void Should_Cache_Nearest_Match()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
|
||||
{
|
||||
var fontManager = FontManager.Current;
|
||||
|
||||
var fontCollection = new TestSystemFontCollection(FontManager.Current);
|
||||
var fontCollection = new TestSystemFontCollection(FontManager.Current.PlatformImpl);
|
||||
|
||||
Assert.True(fontCollection.TryGetGlyphTypeface("Arial", FontStyle.Normal, FontWeight.ExtraBlack, FontStretch.Normal, out var glyphTypeface));
|
||||
|
||||
@@ -62,9 +41,8 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
|
||||
private class TestSystemFontCollection : SystemFontCollection
|
||||
{
|
||||
public TestSystemFontCollection(FontManager fontManager) : base(fontManager)
|
||||
public TestSystemFontCollection(IFontManagerImpl platformImpl) : base(platformImpl)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public IDictionary<string, ConcurrentDictionary<FontCollectionKey, IGlyphTypeface?>> GlyphTypefaceCache => _glyphTypefaceCache;
|
||||
@@ -81,8 +59,6 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
|
||||
var fontCollection = new CustomizableFontCollection(source, source, new[] { fallback });
|
||||
|
||||
fontCollection.Initialize(FontManager.Current.PlatformImpl);
|
||||
|
||||
Assert.True(fontCollection.TryMatchCharacter('A', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var match));
|
||||
|
||||
Assert.Equal("Arial", match.FontFamily.Name);
|
||||
@@ -100,8 +76,6 @@ namespace Avalonia.Skia.UnitTests.Media
|
||||
|
||||
var fontCollection = new CustomizableFontCollection(key, key, null, new[] { ignorable });
|
||||
|
||||
fontCollection.Initialize(FontManager.Current.PlatformImpl);
|
||||
|
||||
var typeface = new Typeface(ignorable);
|
||||
|
||||
var glyphTypeface = typeface.GlyphTypeface;
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.TextFormatting;
|
||||
using Avalonia.Media.TextFormatting.Unicode;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.UnitTests;
|
||||
using Avalonia.Utilities;
|
||||
using Xunit;
|
||||
@@ -1352,8 +1353,17 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
|
||||
.With(renderInterface: new PlatformRenderInterface(),
|
||||
textShaperImpl: new TextShaperImpl()));
|
||||
|
||||
var customFontManagerImpl = new CustomFontManagerImpl();
|
||||
|
||||
AvaloniaLocator.CurrentMutable
|
||||
.Bind<FontManager>().ToConstant(new FontManager(new CustomFontManagerImpl()));
|
||||
.Bind<IFontManagerImpl>().ToConstant(customFontManagerImpl);
|
||||
|
||||
var fontManager = new FontManager(customFontManagerImpl);
|
||||
|
||||
AvaloniaLocator.CurrentMutable
|
||||
.Bind<FontManager>().ToConstant(fontManager);
|
||||
|
||||
fontManager.AddFontCollection(customFontManagerImpl.SystemFonts);
|
||||
|
||||
return disposable;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Avalonia.UnitTests
|
||||
}
|
||||
|
||||
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
|
||||
FontStretch fontStretch, CultureInfo culture, out Typeface fontKey)
|
||||
FontStretch fontStretch, string? familyName, CultureInfo? culture, out Typeface fontKey)
|
||||
{
|
||||
foreach (var customTypeface in _customTypefaces)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user