Skip to content

Optimize FontSourceCollection creation from a filesystem directory, reduce allocs #9844

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using MS.Internal.PresentationCore;

using Microsoft.Win32.SafeHandles;
using System.IO.Enumeration;

namespace MS.Internal.FontCache
{
Expand Down Expand Up @@ -265,25 +266,7 @@ internal static class Util
{
internal const int nullOffset = -1;

internal static string CompositeFontExtension
{
get
{
return SupportedExtensions[0];
}
}

private static readonly string[] SupportedExtensions = new string[]
{
// .COMPOSITEFONT must remain the first entry in this array
// because IsSupportedFontExtension and IsCompositeFont relies on this.
".COMPOSITEFONT",
".OTF",
".TTC",
".TTF",
".TTE"
};

internal const string CompositeFontExtension = ".COMPOSITEFONT";

private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars();

Expand Down Expand Up @@ -518,24 +501,68 @@ internal static Uri CombineUriWithFaceIndex(string fontUri, int faceIndex)
return new Uri(canonicalPathUri + '#' + faceIndexString);
}

internal static bool IsSupportedFontExtension(string extension, out bool isComposite)
/// <summary> Compares the input value with ".OTF", ".TTC", ".TTF", ".TTE" extensions in case-insensitive manner.
/// <para> Also returns in <paramref name="isComposite"/> whether the extension <see cref="IsCompositeFont(ReadOnlySpan{char})"/>.
/// </para> </summary>
/// <param name="extension">The extension string to compare against.</param>
/// <param name="isComposite"><see langword="true"/> is the extension is a composite font, <see langword="false"/> otherwise.</param>
/// <returns><see langword="true"/> if the provided extension is one of the supported ones, <see langword="false"/> otherwise.</returns>
internal static bool IsSupportedFontExtension(ReadOnlySpan<char> extension, out bool isComposite)
{
for (int i = 0; i < SupportedExtensions.Length; ++i)
if (isComposite = IsCompositeFont(extension))
return true;

return extension switch
{
string supportedExtension = SupportedExtensions[i];
if (string.Equals(extension, supportedExtension, StringComparison.OrdinalIgnoreCase))
{
isComposite = (i == 0); // First array entry is *.CompositeFont
return true;
}
}
isComposite = false;
return false;
_ when extension.Equals(".OTF", StringComparison.OrdinalIgnoreCase) => true,
_ when extension.Equals(".TTC", StringComparison.OrdinalIgnoreCase) => true,
_ when extension.Equals(".TTF", StringComparison.OrdinalIgnoreCase) => true,
_ when extension.Equals(".TTE", StringComparison.OrdinalIgnoreCase) => true,
_ => false
};
}

internal static bool IsCompositeFont(string extension)
/// <summary>
/// Compares the input value with ".COMPOSITEFONT" extension in case-insensitive manner.
/// </summary>
/// <param name="extension">The extension string to compare against.</param>
/// <returns><see langword="true"/> if the provided extension is a composite font, <see langword="false"/> otherwise.</returns>
internal static bool IsCompositeFont(ReadOnlySpan<char> extension)
{
return (string.Equals(extension, CompositeFontExtension, StringComparison.OrdinalIgnoreCase));
return extension.Equals(CompositeFontExtension, StringComparison.OrdinalIgnoreCase);
}

/// <summary>
/// Returns path extension from the given <paramref name="uri"/>.
/// </summary>
/// <param name="uri">The uri to extract extension from.</param>
/// <returns>A <see cref="ReadOnlySpan{char}"/> containing the path extension.</returns>
internal static ReadOnlySpan<char> GetUriExtension(Uri uri)
{
return Path.GetExtension(uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).AsSpan());
}

/// <summary> Enumerates files in the directory specified by <paramref name="path"/> similarly as <see cref="Directory.GetFiles(string)"/> would.
/// <para> Only files which are supported based on their extension (calling <see cref="IsSupportedFontExtension(ReadOnlySpan{char}, out bool)"/>) are returned.
/// </para> </summary>
/// <param name="path">The directory where to enumerate files from.</param>
/// <returns>An enumerator of supported font files in the specified directory.</returns>
internal static FileSystemEnumerable<string> EnumerateFontsInDirectory(string path)
{
return new FileSystemEnumerable<string>(path, (ref FileSystemEntry entry) => entry.ToSpecifiedFullPath(),
new EnumerationOptions { AttributesToSkip = FileAttributes.None, IgnoreInaccessible = false })
{
ShouldIncludePredicate = (ref FileSystemEntry entry) =>
{
if (entry.IsDirectory)
return false;

if (IsSupportedFontExtension(Path.GetExtension(entry.FileName), out _))
return true;

return false;
}
};
}

internal static bool IsEnumerableFontUriScheme(Uri fontLocation)
Expand Down Expand Up @@ -579,12 +606,6 @@ internal static bool IsAppSpecificUri(Uri fontLocation)
return !fontLocation.IsAbsoluteUri || !fontLocation.IsFile || fontLocation.IsUnc;
}

internal static string GetUriExtension(Uri uri)
{
string unescapedPath = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
return Path.GetExtension(unescapedPath);
}

/// <summary>
/// Converts the specified portion of a friendly name to a normalized font family reference.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,7 @@ private static void AddResourceToFolderMap(Dictionary<string, List<string>> fold
fileName = resourceFullName.Substring(indexOfLastSlash + 1);
}

string extension = Path.GetExtension(fileName);

bool isComposite;
if (!Util.IsSupportedFontExtension(extension, out isComposite))
if (!Util.IsSupportedFontExtension(Path.GetExtension(fileName.AsSpan()), out _))
return;

// We add entries for a font file to two collections:
Expand Down
Loading