diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index b8a8479a49..74bf3ec4bb 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -11,6 +11,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Fonts/Aspergit_Bold.ttf b/samples/ControlCatalog/Fonts/Aspergit_Bold.ttf new file mode 100644 index 0000000000..c9d6b840e9 Binary files /dev/null and b/samples/ControlCatalog/Fonts/Aspergit_Bold.ttf differ diff --git a/samples/ControlCatalog/Fonts/Aspergit_Bold_Italic.ttf b/samples/ControlCatalog/Fonts/Aspergit_Bold_Italic.ttf new file mode 100644 index 0000000000..a5ba97f377 Binary files /dev/null and b/samples/ControlCatalog/Fonts/Aspergit_Bold_Italic.ttf differ diff --git a/samples/ControlCatalog/Fonts/Aspergit_Italic.ttf b/samples/ControlCatalog/Fonts/Aspergit_Italic.ttf new file mode 100644 index 0000000000..d74ec43df2 Binary files /dev/null and b/samples/ControlCatalog/Fonts/Aspergit_Italic.ttf differ diff --git a/samples/ControlCatalog/Fonts/BolshevikBd.ttf b/samples/ControlCatalog/Fonts/BolshevikBd.ttf new file mode 100644 index 0000000000..359ee1bc15 Binary files /dev/null and b/samples/ControlCatalog/Fonts/BolshevikBd.ttf differ diff --git a/samples/ControlCatalog/Fonts/PinkHighwayBold.ttf b/samples/ControlCatalog/Fonts/PinkHighwayBold.ttf new file mode 100644 index 0000000000..6bb2583632 Binary files /dev/null and b/samples/ControlCatalog/Fonts/PinkHighwayBold.ttf differ diff --git a/samples/ControlCatalog/Fonts/PixelOperator-Bold.ttf b/samples/ControlCatalog/Fonts/PixelOperator-Bold.ttf new file mode 100644 index 0000000000..154890a659 Binary files /dev/null and b/samples/ControlCatalog/Fonts/PixelOperator-Bold.ttf differ diff --git a/samples/ControlCatalog/Fonts/PixelOperator.ttf b/samples/ControlCatalog/Fonts/PixelOperator.ttf new file mode 100644 index 0000000000..277c408b58 Binary files /dev/null and b/samples/ControlCatalog/Fonts/PixelOperator.ttf differ diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml index f0705362d7..faebc3a61e 100644 --- a/samples/ControlCatalog/Pages/BorderPage.xaml +++ b/samples/ControlCatalog/Pages/BorderPage.xaml @@ -1,32 +1,38 @@ - - Border - A control which decorates a child with a border and background + + Border + A control which decorates a child with a border and background - - - Border - - + Border + + - Border and Background - - Border and Background + + - Rounded Corners - - Rounded Corners + + - Rounded Corners - + + Rounded Corners + Rounded Corners + Rounded Corners + Rounded Corners + Rounded Corners + + + - \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index 7442619b5c..db4ecb2189 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -2,41 +2,71 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Avalonia.Platform; namespace Avalonia.Media { public class FontFamily { - public FontFamily(string familyName = "Courier New", Uri baseUri = null) + public FontFamily(string name = "Courier New") { - if (familyName == null) throw new ArgumentNullException(nameof(familyName)); - - Key = new FontFamilyKey(familyName, baseUri); + Name = name ?? throw new ArgumentNullException(nameof(name)); } - public string Name => Key.FriendlyName; + public FontFamily(string name, Uri source) : this(name) + { + Key = new FontFamilyKey(source); + } - public Uri BaseUri => Key.BaseUri; + public string Name { get; } public FontFamilyKey Key { get; } public override string ToString() { - return Key.ToString(); + if (Key != null) + { + return Key + "#" + Name; + } + + return Name; } } public class FontFamilyKey { - public FontFamilyKey(string friendlyName, Uri baseUri = null) + public FontFamilyKey(Uri source) { - FriendlyName = friendlyName; - BaseUri = baseUri; + if (source.AbsolutePath.Contains(".ttf")) + { + var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", ""); + + if (source.Scheme == "resm") + { + var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last(); + FileName = fileNameWithoutExtension + ".ttf"; + Location = new Uri(source.OriginalString.Replace("." + FileName, ""), UriKind.RelativeOrAbsolute); + } + + if (source.Scheme == "res") + { + FileName = source.AbsolutePath.Split('/').Last(); + } + } + else + { + Location = source; + } } - public string FriendlyName { get; } + public Uri Location { get; } - public Uri BaseUri { get; } + public string FileName { get; } public override int GetHashCode() { @@ -44,14 +74,14 @@ namespace Avalonia.Media { var hash = (int)2166136261; - if (FriendlyName != null) + if (Location != null) { - hash = (hash * 16777619) ^ FriendlyName.GetHashCode(); + hash = (hash * 16777619) ^ Location.GetHashCode(); } - if (BaseUri != null) + if (FileName != null) { - hash = (hash * 16777619) ^ BaseUri.GetHashCode(); + hash = (hash * 16777619) ^ FileName.GetHashCode(); } return hash; @@ -62,117 +92,242 @@ namespace Avalonia.Media { if (!(obj is FontFamilyKey other)) return false; - if (FriendlyName != other.FriendlyName) return false; + if (Location != other.Location) return false; - if (BaseUri != other.BaseUri) return false; + if (FileName != other.FileName) return false; return true; } public override string ToString() { - if (BaseUri != null) + if (FileName != null) { - return BaseUri + "#" + FriendlyName; + if (Location.Scheme == "resm") + { + return Location + "." + FileName; + } + + return Location + "/" + FileName; } - return FriendlyName; + return Location.ToString(); } } - //public class FamilyTypeface - //{ - // public FamilyTypeface(Uri resourceUri = null, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal) - // { - // ResourceUri = resourceUri; - // FontStyle = fontStyle; - // FontWeight = fontWeight; - // } - - // public Uri ResourceUri { get; } - // public FontWeight FontWeight { get; } - // public FontStyle FontStyle { get; } - //} - - //public class FamilyTypefaceKey - //{ - // public FamilyTypefaceKey(FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal) - // { - // FontWeight = fontWeight; - // FontStyle = fontStyle; - // } - - // public FontWeight FontWeight { get; } - - // public FontStyle FontStyle { get; } - - // public override int GetHashCode() - // { - // unchecked - // { - // var hash = (int)2166136261; - - // hash = (hash * 16777619) ^ FontWeight.GetHashCode(); - - // hash = (hash * 16777619) ^ FontStyle.GetHashCode(); - - // return hash; - // } - // } - - // public override bool Equals(object obj) - // { - // if (!(obj is FamilyTypefaceKey other)) return false; - - // if (FontWeight != other.FontWeight) return false; - - // if (FontStyle != other.FontStyle) return false; - - // return true; - // } - //} - - //public class CachedFontFamily - //{ - // private readonly ConcurrentDictionary _typefaces = - // new ConcurrentDictionary(); - - // public bool TryGetFamilyTypeface(out FamilyTypeface typeface, FontWeight fontWeight = FontWeight.Normal, - // FontStyle fontStyle = FontStyle.Normal) - // { - // return _typefaces.TryGetValue(new FamilyTypefaceKey(fontWeight, fontStyle), out typeface); - // } - - // public bool TryAddFamilyTypeface(Uri resourceUri, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal) - // { - // var familyTypefaceKeytypefaceKey = new FamilyTypefaceKey(fontWeight, fontStyle); - - // return _typefaces.TryAdd(familyTypefaceKeytypefaceKey, CreateFamilyTypeface(familyTypefaceKeytypefaceKey, resourceUri)); - // } - - // private static FamilyTypeface CreateFamilyTypeface(FamilyTypefaceKey familyTypefaceKey, Uri resourceUri) - // { - // return new FamilyTypeface(resourceUri, familyTypefaceKey.FontWeight, familyTypefaceKey.FontStyle); - // } - //} - - //public class FontFamilyCache - //{ - // private readonly ConcurrentDictionary _cachedFontFamilies = new ConcurrentDictionary(); - - // public bool TryGetCachedFontFamily(FontFamily fontFamily, out CachedFontFamily cachedFontFamily) - // { - // return _cachedFontFamilies.TryGetValue(fontFamily.Key, out cachedFontFamily); - // } - - // public CachedFontFamily GetOrAddCachedFontFamily(FontFamily fontFamily) - // { - // return _cachedFontFamilies.GetOrAdd(fontFamily.Key, CreateCachedFontFamily); - // } - - // private static CachedFontFamily CreateCachedFontFamily(FontFamilyKey fontFamilyKey) - // { - // return new CachedFontFamily(); - // } - //} + public class FontResource + { + public FontResource(Uri source) + { + Source = source; + } + + public Uri Source { get; } + } + + public class FontResourceCollection + { + private Dictionary _fontResources; + private readonly IFontResourceLoader _fontResourceLoader = new FontResourceLoader(); + + public FontResourceCollection(FontFamilyKey key) + { + Key = key; + } + + public FontFamilyKey Key { get; } + + public IEnumerable FontResources + { + get + { + if (_fontResources == null) + { + _fontResources = CreateFontResources(); + } + + return _fontResources.Values; + } + } + + private Dictionary CreateFontResources() + { + return _fontResourceLoader.GetFontResources(Key).ToDictionary(x => x.Source); + } + } + + public interface IFontResourceLoader + { + IEnumerable GetFontResources(FontFamilyKey fontFamilyKey); + } + + public class FontResourceLoader : IFontResourceLoader + { + private static readonly Dictionary s_assemblyNameCache + = new Dictionary(); + + private readonly AssemblyDescriptor _defaultAssembly; + + public FontResourceLoader(Assembly assembly = null) + { + if (assembly == null) + { + assembly = Assembly.GetEntryAssembly(); + } + if (assembly != null) + { + _defaultAssembly = new AssemblyDescriptor(assembly); + } + } + + public IEnumerable GetFontResources(FontFamilyKey fontFamilyKey) + { + return fontFamilyKey.FileName != null + ? GetFontResourcesByFileName(fontFamilyKey.Location, fontFamilyKey.FileName) + : GetFontResourcesByLocation(fontFamilyKey.Location); + } + + private IEnumerable GetFontResourcesByLocation(Uri location) + { + var assembly = GetAssembly(location); + + var locationPath = GetLocationPath(location); + + var matchingResources = assembly.Resources.Where(x => x.Contains(locationPath)); + + return matchingResources.Select(x => new FontResource(GetResourceUri(x, assembly.Name))); + } + + private IEnumerable GetFontResourcesByFileName(Uri location, string fileName) + { + var assembly = GetAssembly(location); + + var compareTo = fileName.Split('*').First(); + + var matchingResources = assembly.Resources.Where(x => x.Contains(compareTo)); + + return matchingResources.Select(x => new FontResource(GetResourceUri(x, assembly.Name))); + } + + private static Uri GetResourceUri(string path, string assemblyName) + { + return new Uri("resm:" + path + "?assembly=" + assemblyName); + } + + private static string GetLocationPath(Uri uri) + { + if (uri.Scheme == "resm") return uri.AbsolutePath; + + var path = new StringBuilder(); + + for (var index = 0; index < uri.Segments.Length; index++) + { + path.Append(uri.Segments[index]); + if (index < uri.Segments.Length - 1) + { + path.Append('.'); + } + } + + return path.ToString(); + } + + private AssemblyDescriptor GetAssembly(Uri uri) + { + if (uri == null) return null; + + var parameters = ParseParameters(uri); + + return parameters.TryGetValue("assembly", out var assemblyName) ? GetAssembly(assemblyName) : null; + } + + private AssemblyDescriptor GetAssembly(string name) + { + if (name == null) + { + return _defaultAssembly; + } + + if (!s_assemblyNameCache.TryGetValue(name, out var rv)) + { + var loadedAssemblies = AvaloniaLocator.Current.GetService().GetLoadedAssemblies(); + var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name); + if (match != null) + { + s_assemblyNameCache[name] = rv = new AssemblyDescriptor(match); + } + else + { + // iOS does not support loading assemblies dynamically! + // +#if NETCOREAPP1_0 + s_assemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(new AssemblyName(name))); +#elif __IOS__ + throw new InvalidOperationException( + $"Assembly {name} needs to be referenced and explicitly loaded before loading resources"); +#else + s_assemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name)); +#endif + } + } + + return rv; + } + + private static Dictionary ParseParameters(Uri uri) + { + return uri.Query.TrimStart('?') + .Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries) + .Select(p => p.Split('=')) + .ToDictionary(p => p[0], p => p[1]); + } + + private class AssemblyDescriptor + { + public AssemblyDescriptor(Assembly assembly) + { + Assembly = assembly; + + if (Assembly == null) return; + + Resources = assembly.GetManifestResourceNames().ToList(); + + Name = Assembly.GetName().Name; + } + + public string Name { get; } + public Assembly Assembly { get; } + public List Resources { get; } + } + } + + public class CachedFontFamily + { + private readonly FontResourceCollection _fontResourceCollection; + + public CachedFontFamily(FontFamilyKey key, FontResourceCollection fontResourceCollection) + { + Key = key; + _fontResourceCollection = fontResourceCollection; + } + + public FontFamilyKey Key { get; } + + public IEnumerable FontResources => _fontResourceCollection.FontResources; + } + + public class FontFamilyCache + { + private static readonly ConcurrentDictionary s_cachedFontFamilies = new ConcurrentDictionary(); + + public CachedFontFamily GetOrAddFontFamily(FontFamilyKey key) + { + return s_cachedFontFamilies.GetOrAdd(key, CreateCachedFontFamily); + } + + private static CachedFontFamily CreateCachedFontFamily(FontFamilyKey fontFamilyKey) + { + return new CachedFontFamily(fontFamilyKey, new FontResourceCollection(fontFamilyKey)); + } + } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs index 6cd7c24632..59742f8b12 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs @@ -18,13 +18,16 @@ namespace Avalonia.Markup.Xaml.Converters if (string.IsNullOrEmpty(s)) throw new ArgumentException("Specified family is not supported."); - if (!s.StartsWith("resm:")) return new FontFamily(s); - var fontFamilyExpression = s.Split('#'); + if (fontFamilyExpression.Length == 1) + { + return new FontFamily(s); + } + string familyName; - Uri baseUri = null; + Uri source = null; switch (fontFamilyExpression.Length) { @@ -35,7 +38,7 @@ namespace Avalonia.Markup.Xaml.Converters } case 2: { - baseUri = new Uri(fontFamilyExpression[0], UriKind.RelativeOrAbsolute); + source = new Uri(fontFamilyExpression[0], UriKind.RelativeOrAbsolute); familyName = fontFamilyExpression[1]; break; } @@ -45,7 +48,7 @@ namespace Avalonia.Markup.Xaml.Converters } } - return new FontFamily(familyName, baseUri); + return new FontFamily(familyName, source); } } } \ No newline at end of file diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 4287fd0b9e..659a72e9a5 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -106,9 +106,8 @@ namespace Avalonia.Shared.PlatformSupport if (uri != null) { var qs = ParseQueryString(uri); - string assemblyName; - if (qs.TryGetValue("assembly", out assemblyName)) + if (qs.TryGetValue("assembly", out var assemblyName)) { return GetAssembly(assemblyName); } @@ -124,8 +123,7 @@ namespace Avalonia.Shared.PlatformSupport return _defaultAssembly; } - AssemblyDescriptor rv; - if (!AssemblyNameCache.TryGetValue(name, out rv)) + if (!AssemblyNameCache.TryGetValue(name, out var rv)) { var loadedAssemblies = AvaloniaLocator.Current.GetService().GetLoadedAssemblies(); var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name); @@ -199,5 +197,5 @@ namespace Avalonia.Shared.PlatformSupport public Dictionary Resources { get; } public string Name { get; } } - } + } } diff --git a/src/Skia/Avalonia.Skia/TypefaceCache.cs b/src/Skia/Avalonia.Skia/TypefaceCache.cs index ed84d4818e..f4598df8ec 100644 --- a/src/Skia/Avalonia.Skia/TypefaceCache.cs +++ b/src/Skia/Avalonia.Skia/TypefaceCache.cs @@ -56,21 +56,21 @@ namespace Avalonia.Skia if (!entry.TryGetValue(key, out var typeface)) { - if (fontFamily.BaseUri != null) - { - var stream = AvaloniaLocator.Current.GetService().Open(fontFamily.BaseUri); - - typeface = SKTypeface.FromStream(stream); - } - else - { + //if (fontFamily.BaseUri != null) + //{ + // var stream = AvaloniaLocator.Current.GetService().Open(fontFamily.BaseUri); + + // typeface = SKTypeface.FromStream(stream); + //} + //else + //{ typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant); if (typeface == null) { typeface = SKTypeface.FromFamilyName(null, SKTypefaceStyle.Normal); } - } + //} entry[key] = typeface; } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index c7356fde1f..732089b79d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection; using Avalonia.Media; using Avalonia.Platform; using SharpDX; @@ -24,13 +26,17 @@ namespace Avalonia.Direct2D1.Media { Text = text; - var factory = AvaloniaLocator.Current.GetService(); + var factory = AvaloniaLocator.Current.GetService(); DWrite.TextFormat textFormat; - if (typeface.FontFamily.BaseUri != null) + if (typeface.FontFamily.Key != null) { - var fontCollection = Direct2D1CustomFontResourceCache.GetOrAddCustomFontResource(typeface.FontFamily, factory); + var fontFamilyCache = new FontFamilyCache(); + + var fontFamily = fontFamilyCache.GetOrAddFontFamily(typeface.FontFamily.Key); + + var fontCollection = Direct2D1CustomFontCollectionCache.GetOrAddCustomFontCollection(fontFamily, factory); textFormat = new DWrite.TextFormat( factory, @@ -81,11 +87,6 @@ namespace Avalonia.Direct2D1.Media public DWrite.TextLayout TextLayout { get; } - //public void Dispose() - //{ - // TextLayout.Dispose(); - //} - public IEnumerable GetLines() { var result = TextLayout.GetLineMetrics(); @@ -148,19 +149,19 @@ namespace Avalonia.Direct2D1.Media } } - internal static class Direct2D1CustomFontResourceCache + internal static class Direct2D1CustomFontCollectionCache { private static readonly ConcurrentDictionary s_cachedFonts = new ConcurrentDictionary(); - public static DWrite.FontCollection GetOrAddCustomFontResource(FontFamily fontFamily, DWrite.Factory factory) + public static DWrite.FontCollection GetOrAddCustomFontCollection(CachedFontFamily fontFamily, DWrite.Factory factory) { - return s_cachedFonts.GetOrAdd(fontFamily.Key, x => CreateCustomFontResource(x, factory)); + return s_cachedFonts.GetOrAdd(fontFamily.Key, x => CreateCustomFontCollection(fontFamily, factory)); } - private static DWrite.FontCollection CreateCustomFontResource(FontFamilyKey fontFamilyKey, DWrite.Factory factory) + private static DWrite.FontCollection CreateCustomFontCollection(CachedFontFamily fontFamily, DWrite.Factory factory) { - var fontLoader = new ResourceFontLoader(factory, fontFamilyKey.BaseUri); + var fontLoader = new ResourceFontLoader(factory, fontFamily.FontResources); return new DWrite.FontCollection(factory, fontLoader, fontLoader.Key); } @@ -172,27 +173,29 @@ namespace Avalonia.Direct2D1.Media private readonly List _enumerators = new List(); private readonly DataStream _keyStream; - /// /// Initializes a new instance of the class. /// /// The factory. - /// - public ResourceFontLoader(DWrite.Factory factory, Uri fontResource) + /// + public ResourceFontLoader(DWrite.Factory factory, IEnumerable fontResources) { var factory1 = factory; - var assets = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetService(); - var resourceStream = assets.Open(fontResource); + foreach (var font in fontResources) + { + var resourceStream = assetLoader.Open(font.Source); - var dataStream = new DataStream((int)resourceStream.Length, true, true); + var dataStream = new DataStream((int)resourceStream.Length, true, true); - resourceStream.CopyTo(dataStream); + resourceStream.CopyTo(dataStream); - dataStream.Position = 0; + dataStream.Position = 0; - _fontStreams.Add(new ResourceFontFileStream(dataStream)); + _fontStreams.Add(new ResourceFontFileStream(dataStream)); + } // Build a Key storage that stores the index of the font _keyStream = new DataStream(sizeof(int) * _fontStreams.Count, true, true);