File: Fonts\FontRegistrar.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.Maui
{
	/// <inheritdoc cref="IFontRegistrar"/>
	public partial class FontRegistrar : IFontRegistrar
	{
		readonly Dictionary<string, (string Filename, string? Alias, Assembly Assembly)> _embeddedFonts = new(StringComparer.Ordinal);
		readonly Dictionary<string, (string Filename, string? Alias)> _nativeFonts = new(StringComparer.Ordinal);
		readonly Dictionary<string, string?> _fontLookupCache = new(StringComparer.Ordinal);
		readonly IServiceProvider? _serviceProvider;
 
		IEmbeddedFontLoader _fontLoader;
 
		/// <summary>
		/// Creates a new instance of <see cref="FontRegistrar"/>.
		/// </summary>
		/// <param name="fontLoader">An instance of <see cref="IEmbeddedFontLoader"/> that is responsible for actually loading fonts.</param>
		/// <param name="serviceProvider">A reference to the app <see cref="IServiceProvider"/>.
		/// Typically this should be provided through dependency injection for logging purposes, otherwise can be ignored.</param>
		public FontRegistrar(IEmbeddedFontLoader fontLoader, IServiceProvider? serviceProvider = null)
		{
			_fontLoader = fontLoader;
			_serviceProvider = serviceProvider;
		}
 
		/// <inheritdoc/>
		public void Register(string filename, string? alias, Assembly assembly)
		{
			_embeddedFonts[filename] = (filename, alias, assembly);
 
			if (!string.IsNullOrWhiteSpace(alias))
				_embeddedFonts[alias!] = (filename, alias, assembly);
		}
 
		/// <inheritdoc/>
		public void Register(string filename, string? alias)
		{
			_nativeFonts[filename] = (filename, alias);
 
			if (!string.IsNullOrWhiteSpace(alias))
				_nativeFonts[alias!] = (filename, alias);
		}
 
		/// <inheritdoc/>
		public string? GetFont(string font)
		{
			if (_fontLookupCache.TryGetValue(font, out var foundResult))
				return foundResult;
 
			try
			{
				if (_embeddedFonts.TryGetValue(font, out var foundFont))
				{
					using var stream = GetEmbeddedResourceStream(foundFont);
 
					return LoadEmbeddedFont(font, foundFont.Filename, foundFont.Alias, stream);
				}
				else if (_nativeFonts.TryGetValue(font, out var foundNativeFont))
				{
					return LoadNativeAppFont(font, foundNativeFont.Filename, foundNativeFont.Alias);
				}
			}
			catch (Exception ex)
			{
				_serviceProvider?.CreateLogger<FontRegistrar>()?.LogWarning(ex, "Unable to load font '{Font}'.", font);
			}
 
			return _fontLookupCache[font] = null;
		}
 
		string? LoadFileSystemFont(string cacheKey, string filename, string? alias)
		{
			var font = new EmbeddedFont { FontName = filename };
 
			if (_fontLoader == null)
				throw new InvalidOperationException("Font loader was not set on the font registrar.");
 
			var result = _fontLoader.LoadFont(font);
 
			return _fontLookupCache[cacheKey] = result;
		}
 
		string? LoadEmbeddedFont(string cacheKey, string filename, string? alias, Stream stream)
		{
			var font = new EmbeddedFont { FontName = filename, ResourceStream = stream };
 
			if (_fontLoader == null)
				throw new InvalidOperationException("Font loader was not set on the font registrar.");
 
			var result = _fontLoader.LoadFont(font);
 
			return _fontLookupCache[cacheKey] = result;
		}
 
		static Stream GetEmbeddedResourceStream((string Filename, string? Alias, Assembly Assembly) embeddedFont)
		{
			var resourceNames = embeddedFont.Assembly.GetManifestResourceNames();
			var searchName = "." + embeddedFont.Filename;
 
			foreach (var name in resourceNames)
			{
				if (name.EndsWith(searchName, StringComparison.CurrentCultureIgnoreCase))
					return embeddedFont.Assembly.GetManifestResourceStream(name)!;
			}
 
			throw new FileNotFoundException($"Resource ending with {embeddedFont.Filename} not found.");
		}
	}
}