// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; namespace SixLabors.Fonts { /// /// Provides a collection of fonts. /// internal sealed class SystemFontCollection : IReadOnlySystemFontCollection, IReadOnlyFontMetricsCollection { private readonly FontCollection collection; private readonly IReadOnlyCollection searchDirectories; /// /// Gets the default set of locations we probe for System Fonts. /// private static readonly IReadOnlyCollection StandardFontLocations; static SystemFontCollection() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { StandardFontLocations = new[] { @"%SYSTEMROOT%\Fonts", @"%APPDATA%\Microsoft\Windows\Fonts", @"%LOCALAPPDATA%\Microsoft\Windows\Fonts", }; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { StandardFontLocations = new[] { "%HOME%/.fonts/", "%HOME%/.local/share/fonts/", "/usr/local/share/fonts/", "/usr/share/fonts/", }; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { StandardFontLocations = new[] { // As documented on "Mac OS X: Font locations and their purposes" // https://web.archive.org/web/20191015122508/https://support.apple.com/en-us/HT201722 "%HOME%/Library/Fonts/", "/Library/Fonts/", "/System/Library/Fonts/", "/Network/Library/Fonts/", }; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("Android"))) { StandardFontLocations = new[] { "/system/fonts/" }; } else { StandardFontLocations = Array.Empty(); } } public SystemFontCollection() { IEnumerable paths; Native.MacSystemFontsEnumerator? nativeEnumerator = null; bool forceDirectoryEnumeration = AppContext.TryGetSwitch("Switch.SixLabors.Fonts.DoNotUseNativeSystemFontsEnumeration", out bool isEnabled) && isEnabled; if (!forceDirectoryEnumeration && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { nativeEnumerator = new Native.MacSystemFontsEnumerator(); // The CTFontManagerCopyAvailableFontURLs method might return duplicate paths, hence the call to Distinct() paths = nativeEnumerator.Distinct(); this.searchDirectories = Array.Empty(); } else { string[] expanded = [.. StandardFontLocations.Select(Environment.ExpandEnvironmentVariables)]; string[] existingDirectories = [.. expanded.Where(x => Directory.Exists(x))]; // We do this to provide a consistent experience with case sensitive file systems. paths = existingDirectories .SelectMany(x => Directory.EnumerateFiles(x, "*.*", SearchOption.AllDirectories)) .Where(x => Path.GetExtension(x).Equals(".ttf", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(x).Equals(".ttc", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(x).Equals(".otf", StringComparison.OrdinalIgnoreCase)); this.searchDirectories = existingDirectories; } this.collection = CreateSystemFontCollection(paths, this.searchDirectories); nativeEnumerator?.Dispose(); } /// public IEnumerable Families => this.collection.Families; /// public IEnumerable SearchDirectories => this.searchDirectories; /// public FontFamily Get(string name) => this.GetByCulture(name, CultureInfo.InvariantCulture); /// public bool TryGet(string name, out FontFamily family) => this.collection.TryGet(name, out family); /// public IEnumerable GetByCulture(CultureInfo culture) => this.collection.GetByCulture(culture); /// public FontFamily GetByCulture(string name, CultureInfo culture) => this.collection.GetByCulture(name, culture); /// public bool TryGetByCulture(string name, CultureInfo culture, out FontFamily family) => this.collection.TryGetByCulture(name, culture, out family); /// bool IReadOnlyFontMetricsCollection.TryGetMetrics(string name, CultureInfo culture, FontStyle style, [NotNullWhen(true)] out FontMetrics? metrics) => ((IReadOnlyFontMetricsCollection)this.collection).TryGetMetrics(name, culture, style, out metrics); /// IEnumerable IReadOnlyFontMetricsCollection.GetAllMetrics(string name, CultureInfo culture) => ((IReadOnlyFontMetricsCollection)this.collection).GetAllMetrics(name, culture); /// ReadOnlyMemory IReadOnlyFontMetricsCollection.GetAllStyles(string name, CultureInfo culture) => ((IReadOnlyFontMetricsCollection)this.collection).GetAllStyles(name, culture); /// IEnumerator IReadOnlyFontMetricsCollection.GetEnumerator() => ((IReadOnlyFontMetricsCollection)this.collection).GetEnumerator(); private static FontCollection CreateSystemFontCollection(IEnumerable paths, IReadOnlyCollection searchDirectories) { FontCollection collection = new(searchDirectories); foreach (string path in paths) { try { if (path.EndsWith(".ttc", StringComparison.OrdinalIgnoreCase)) { _ = collection.AddCollection(path); } else { _ = collection.Add(path); } } catch { // We swallow exceptions installing system fonts as we hold no guarantees about permissions etc. } } return collection; } } }