39 changed files with 1262 additions and 295 deletions
@ -0,0 +1,6 @@ |
|||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="HarfBuzzSharp" Version="2.6.1-rc.153" /> |
||||
|
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1-rc.153" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -1,6 +1,6 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="SkiaSharp" Version="1.68.0" /> |
<PackageReference Include="SkiaSharp" Version="1.68.1-rc.153" /> |
||||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.2" /> |
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1-rc.153" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -0,0 +1,100 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Media |
||||
|
{ |
||||
|
public abstract class FontManager : IFontManagerImpl |
||||
|
{ |
||||
|
public static readonly FontManager Default = CreateDefaultFontManger(); |
||||
|
|
||||
|
/// <inheritdoc cref="IFontManagerImpl"/>
|
||||
|
public string DefaultFontFamilyName { get; protected set; } |
||||
|
|
||||
|
private static FontManager CreateDefaultFontManger() |
||||
|
{ |
||||
|
var platformImpl = AvaloniaLocator.Current.GetService<IFontManagerImpl>(); |
||||
|
|
||||
|
if(platformImpl == null) |
||||
|
{ |
||||
|
return new EmptyFontManager(); |
||||
|
} |
||||
|
|
||||
|
return new PlatformFontManger(platformImpl); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IFontManagerImpl"/>
|
||||
|
public abstract IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false); |
||||
|
|
||||
|
/// <inheritdoc cref="IFontManagerImpl"/>
|
||||
|
public abstract IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface); |
||||
|
|
||||
|
/// <inheritdoc cref="IFontManagerImpl"/>
|
||||
|
public abstract Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle); |
||||
|
|
||||
|
/// <inheritdoc cref="IFontManagerImpl"/>
|
||||
|
public abstract Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, |
||||
|
FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null); |
||||
|
|
||||
|
private class PlatformFontManger : FontManager |
||||
|
{ |
||||
|
private readonly IFontManagerImpl _platformImpl; |
||||
|
|
||||
|
public PlatformFontManger(IFontManagerImpl platformImpl) |
||||
|
{ |
||||
|
_platformImpl = platformImpl; |
||||
|
|
||||
|
DefaultFontFamilyName = _platformImpl.DefaultFontFamilyName; |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) => |
||||
|
_platformImpl.GetInstalledFontFamilyNames(checkForUpdates); |
||||
|
|
||||
|
public override IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) => _platformImpl.CreateGlyphTypeface(typeface); |
||||
|
|
||||
|
public override Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) => |
||||
|
_platformImpl.GetTypeface(fontFamily, fontWeight, fontStyle); |
||||
|
|
||||
|
public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, |
||||
|
FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null) => |
||||
|
_platformImpl.MatchCharacter(codepoint, fontWeight, fontStyle, fontFamily, culture); |
||||
|
} |
||||
|
|
||||
|
private class EmptyFontManager : FontManager |
||||
|
{ |
||||
|
private readonly string[] _defaultFontFamilies = { "Arial" }; |
||||
|
|
||||
|
public EmptyFontManager() |
||||
|
{ |
||||
|
DefaultFontFamilyName = "Arial"; |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) |
||||
|
{ |
||||
|
return _defaultFontFamilies; |
||||
|
} |
||||
|
|
||||
|
public override IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) |
||||
|
{ |
||||
|
throw new NotSupportedException(); |
||||
|
} |
||||
|
|
||||
|
public override Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) |
||||
|
{ |
||||
|
throw new NotSupportedException(); |
||||
|
} |
||||
|
|
||||
|
public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null) |
||||
|
{ |
||||
|
throw new NotSupportedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Media |
||||
|
{ |
||||
|
public class GlyphTypeface : IDisposable |
||||
|
{ |
||||
|
public GlyphTypeface(Typeface typeface) : this(FontManager.Default.CreateGlyphTypeface(typeface)) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public GlyphTypeface(IGlyphTypefaceImpl platformImpl) |
||||
|
{ |
||||
|
PlatformImpl = platformImpl; |
||||
|
} |
||||
|
|
||||
|
public IGlyphTypefaceImpl PlatformImpl { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the font design units per em.
|
||||
|
/// </summary>
|
||||
|
public short DesignEmHeight => PlatformImpl.DesignEmHeight; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended distance above the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int Ascent => PlatformImpl.Ascent; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended distance under the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int Descent => PlatformImpl.Descent; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended additional space between two lines of text in design em size.
|
||||
|
/// </summary>
|
||||
|
public int LineGap => PlatformImpl.LineGap; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended line height.
|
||||
|
/// </summary>
|
||||
|
public int LineHeight => Descent - Ascent + LineGap; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int UnderlinePosition => PlatformImpl.UnderlinePosition; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the thickness of the underline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int UnderlineThickness => PlatformImpl.UnderlineThickness; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int StrikethroughPosition => PlatformImpl.StrikethroughPosition; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the thickness of the underline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int StrikethroughThickness => PlatformImpl.StrikethroughThickness; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an glyph index for the specified codepoint.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Returns <c>0</c> if a glyph isn't found.
|
||||
|
/// </remarks>
|
||||
|
/// <param name="codepoint">The codepoint.</param>
|
||||
|
/// <returns>
|
||||
|
/// A glyph index.
|
||||
|
/// </returns>
|
||||
|
public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="codepoints">The codepoints to map.</param>
|
||||
|
/// <returns></returns>
|
||||
|
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) => PlatformImpl.GetGlyphs(codepoints); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the glyph advance for the specified glyph.
|
||||
|
/// </summary>
|
||||
|
/// <param name="glyph">The glyph.</param>
|
||||
|
/// <returns>
|
||||
|
/// The advance.
|
||||
|
/// </returns>
|
||||
|
public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an array of glyph advances in design em size.
|
||||
|
/// </summary>
|
||||
|
/// <param name="glyphs">The glyph indices.</param>
|
||||
|
/// <returns></returns>
|
||||
|
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) => PlatformImpl.GetGlyphAdvances(glyphs); |
||||
|
|
||||
|
void IDisposable.Dispose() |
||||
|
{ |
||||
|
PlatformImpl?.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Platform |
||||
|
{ |
||||
|
public interface IFontManagerImpl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the system's default font family's name.
|
||||
|
/// </summary>
|
||||
|
string DefaultFontFamilyName { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get all installed fonts in the system.
|
||||
|
/// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
|
||||
|
/// </summary>
|
||||
|
IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a glyph typeface for specified typeface.
|
||||
|
/// </summary>
|
||||
|
/// <param name="typeface">The typeface.</param>
|
||||
|
/// <returns>
|
||||
|
/// The glyph typeface implementation.
|
||||
|
/// </returns>
|
||||
|
IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get a typeface from specified parameters.
|
||||
|
/// </summary>
|
||||
|
/// <param name="fontFamily">The font family.</param>
|
||||
|
/// <param name="fontWeight">The font weight.</param>
|
||||
|
/// <param name="fontStyle">The font style.</param>
|
||||
|
/// <returns>
|
||||
|
/// The typeface.
|
||||
|
/// </returns>
|
||||
|
Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to match a specified character to a typeface that supports specified font properties.
|
||||
|
/// </summary>
|
||||
|
/// <param name="codepoint">The codepoint to match against.</param>
|
||||
|
/// <param name="fontWeight">The font weight.</param>
|
||||
|
/// <param name="fontStyle">The font style.</param>
|
||||
|
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
|
||||
|
/// <param name="culture">The culture.</param>
|
||||
|
/// <returns>
|
||||
|
/// The typeface.
|
||||
|
/// </returns>
|
||||
|
Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Platform |
||||
|
{ |
||||
|
public interface IGlyphTypefaceImpl : IDisposable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the font design units per em.
|
||||
|
/// </summary>
|
||||
|
short DesignEmHeight { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended distance above the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
int Ascent { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended distance under the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
int Descent { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended additional space between two lines of text in design em size.
|
||||
|
/// </summary>
|
||||
|
int LineGap { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
int UnderlinePosition { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the thickness of the underline in design em size.
|
||||
|
/// </summary>
|
||||
|
int UnderlineThickness { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
int StrikethroughPosition { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the thickness of the underline in design em size.
|
||||
|
/// </summary>
|
||||
|
int StrikethroughThickness { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an glyph index for the specified codepoint.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Returns <c>0</c> if a glyph isn't found.
|
||||
|
/// </remarks>
|
||||
|
/// <param name="codepoint">The codepoint.</param>
|
||||
|
/// <returns>
|
||||
|
/// A glyph index.
|
||||
|
/// </returns>
|
||||
|
ushort GetGlyph(uint codepoint); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="codepoints">The codepoints to map.</param>
|
||||
|
/// <returns>
|
||||
|
/// An array of glyph indices.
|
||||
|
/// </returns>
|
||||
|
ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the glyph advance for the specified glyph.
|
||||
|
/// </summary>
|
||||
|
/// <param name="glyph">The glyph.</param>
|
||||
|
/// <returns>
|
||||
|
/// The advance.
|
||||
|
/// </returns>
|
||||
|
int GetGlyphAdvance(ushort glyph); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an array of glyph advances in design em size.
|
||||
|
/// </summary>
|
||||
|
/// <param name="glyphs">The glyph indices.</param>
|
||||
|
/// <returns>
|
||||
|
/// An array of glyph advances.
|
||||
|
/// </returns>
|
||||
|
int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
internal readonly struct FontKey : IEquatable<FontKey> |
||||
|
{ |
||||
|
public readonly FontStyle Style; |
||||
|
public readonly FontWeight Weight; |
||||
|
|
||||
|
public FontKey(FontWeight weight, FontStyle style) |
||||
|
{ |
||||
|
Style = style; |
||||
|
Weight = weight; |
||||
|
} |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
var hash = 17; |
||||
|
hash = hash * 31 + (int)Style; |
||||
|
hash = hash * 31 + (int)Weight; |
||||
|
|
||||
|
return hash; |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object other) |
||||
|
{ |
||||
|
return other is FontKey key && Equals(key); |
||||
|
} |
||||
|
|
||||
|
public bool Equals(FontKey other) |
||||
|
{ |
||||
|
return Style == other.Style && |
||||
|
Weight == other.Weight; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,91 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
internal class FontManagerImpl : IFontManagerImpl |
||||
|
{ |
||||
|
private SKFontManager _skFontManager = SKFontManager.Default; |
||||
|
|
||||
|
private readonly ConcurrentDictionary<Typeface, GlyphTypefaceImpl> _glyphTypefaceCache = |
||||
|
new ConcurrentDictionary<Typeface, GlyphTypefaceImpl>(); |
||||
|
|
||||
|
public FontManagerImpl() |
||||
|
{ |
||||
|
DefaultFontFamilyName = SKTypeface.Default.FamilyName; |
||||
|
} |
||||
|
|
||||
|
public string DefaultFontFamilyName { get; } |
||||
|
|
||||
|
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) |
||||
|
{ |
||||
|
if (checkForUpdates) |
||||
|
{ |
||||
|
_skFontManager = SKFontManager.CreateDefault(); |
||||
|
} |
||||
|
|
||||
|
return _skFontManager.FontFamilies; |
||||
|
} |
||||
|
|
||||
|
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) |
||||
|
{ |
||||
|
return _glyphTypefaceCache.GetOrAdd(typeface, new GlyphTypefaceImpl(typeface)); |
||||
|
} |
||||
|
|
||||
|
public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) |
||||
|
{ |
||||
|
return TypefaceCache.Get(fontFamily.Name, fontWeight, fontStyle).Typeface; |
||||
|
} |
||||
|
|
||||
|
public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null) |
||||
|
{ |
||||
|
var fontFamilyName = FontFamily.Default.Name; |
||||
|
|
||||
|
if (culture == null) |
||||
|
{ |
||||
|
culture = CultureInfo.CurrentUICulture; |
||||
|
} |
||||
|
|
||||
|
if (fontFamily != null) |
||||
|
{ |
||||
|
foreach (var familyName in fontFamily.FamilyNames) |
||||
|
{ |
||||
|
var skTypeface = _skFontManager.MatchCharacter(familyName, (SKFontStyleWeight)fontWeight, |
||||
|
SKFontStyleWidth.Normal, |
||||
|
(SKFontStyleSlant)fontStyle, |
||||
|
new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint); |
||||
|
|
||||
|
if (skTypeface == null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
fontFamilyName = familyName; |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var skTypeface = _skFontManager.MatchCharacter(null, (SKFontStyleWeight)fontWeight, SKFontStyleWidth.Normal, |
||||
|
(SKFontStyleSlant)fontStyle, |
||||
|
new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint); |
||||
|
|
||||
|
if (skTypeface != null) |
||||
|
{ |
||||
|
fontFamilyName = skTypeface.FamilyName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return GetTypeface(fontFamilyName, fontWeight, fontStyle); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,179 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using HarfBuzzSharp; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
internal class GlyphTypefaceImpl : IGlyphTypefaceImpl |
||||
|
{ |
||||
|
private bool _isDisposed; |
||||
|
|
||||
|
public GlyphTypefaceImpl(Typeface typeface) |
||||
|
{ |
||||
|
Typeface = TypefaceCache.Get(typeface.FontFamily, typeface.Weight, typeface.Style).SKTypeface; |
||||
|
|
||||
|
Face = new Face(GetTable) |
||||
|
{ |
||||
|
UnitsPerEm = Typeface.UnitsPerEm |
||||
|
}; |
||||
|
|
||||
|
Font = new Font(Face); |
||||
|
|
||||
|
Font.SetFunctionsOpenType(); |
||||
|
|
||||
|
Font.GetScale(out var xScale, out _); |
||||
|
|
||||
|
DesignEmHeight = (short)xScale; |
||||
|
|
||||
|
if (!Font.TryGetHorizontalFontExtents(out var fontExtents)) |
||||
|
{ |
||||
|
Font.TryGetVerticalFontExtents(out fontExtents); |
||||
|
} |
||||
|
|
||||
|
Ascent = -fontExtents.Ascender; |
||||
|
|
||||
|
Descent = -fontExtents.Descender; |
||||
|
|
||||
|
LineGap = fontExtents.LineGap; |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition)) |
||||
|
{ |
||||
|
UnderlinePosition = underlinePosition; |
||||
|
} |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness)) |
||||
|
{ |
||||
|
UnderlineThickness = underlineThickness; |
||||
|
} |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition)) |
||||
|
{ |
||||
|
StrikethroughPosition = strikethroughPosition; |
||||
|
} |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness)) |
||||
|
{ |
||||
|
StrikethroughThickness = strikethroughThickness; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Face Face { get; } |
||||
|
|
||||
|
public Font Font { get; } |
||||
|
|
||||
|
public SKTypeface Typeface { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public short DesignEmHeight { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int Ascent { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int Descent { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int LineGap { get; } |
||||
|
|
||||
|
//ToDo: Get these values from HarfBuzz
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int UnderlinePosition { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int UnderlineThickness { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int StrikethroughPosition { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int StrikethroughThickness { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public ushort GetGlyph(uint codepoint) |
||||
|
{ |
||||
|
if (Font.TryGetGlyph(codepoint, out var glyph)) |
||||
|
{ |
||||
|
return (ushort)glyph; |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) |
||||
|
{ |
||||
|
var glyphs = new ushort[codepoints.Length]; |
||||
|
|
||||
|
for (var i = 0; i < codepoints.Length; i++) |
||||
|
{ |
||||
|
if (Font.TryGetGlyph(codepoints[i], out var glyph)) |
||||
|
{ |
||||
|
glyphs[i] = (ushort)glyph; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return glyphs; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int GetGlyphAdvance(ushort glyph) |
||||
|
{ |
||||
|
return Font.GetHorizontalGlyphAdvance(glyph); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) |
||||
|
{ |
||||
|
var glyphIndices = new uint[glyphs.Length]; |
||||
|
|
||||
|
for (var i = 0; i < glyphs.Length; i++) |
||||
|
{ |
||||
|
glyphIndices[i] = glyphs[i]; |
||||
|
} |
||||
|
|
||||
|
return Font.GetHorizontalGlyphAdvances(glyphIndices); |
||||
|
} |
||||
|
|
||||
|
private Blob GetTable(Face face, Tag tag) |
||||
|
{ |
||||
|
var size = Typeface.GetTableSize(tag); |
||||
|
|
||||
|
var data = Marshal.AllocCoTaskMem(size); |
||||
|
|
||||
|
var releaseDelegate = new ReleaseDelegate(() => Marshal.FreeCoTaskMem(data)); |
||||
|
|
||||
|
return Typeface.TryGetTableData(tag, 0, size, data) ? |
||||
|
new Blob(data, size, MemoryMode.ReadOnly, releaseDelegate) : null; |
||||
|
} |
||||
|
|
||||
|
private void Dispose(bool disposing) |
||||
|
{ |
||||
|
if (_isDisposed) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_isDisposed = true; |
||||
|
|
||||
|
if (!disposing) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Font?.Dispose(); |
||||
|
Face?.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
Dispose(true); |
||||
|
GC.SuppressFinalize(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using Avalonia.Media; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
internal class TypefaceCollectionEntry |
||||
|
{ |
||||
|
public TypefaceCollectionEntry(Typeface typeface, SKTypeface skTypeface) |
||||
|
{ |
||||
|
Typeface = typeface; |
||||
|
SKTypeface = skTypeface; |
||||
|
} |
||||
|
public Typeface Typeface { get; } |
||||
|
public SKTypeface SKTypeface { get; } |
||||
|
} |
||||
|
} |
||||
@ -1,62 +1,61 @@ |
|||||
using System.Collections.Concurrent; |
using System.Collections.Concurrent; |
||||
using Avalonia.Media; |
using Avalonia.Media; |
||||
using Avalonia.Media.Fonts; |
using Avalonia.Media.Fonts; |
||||
|
using SharpDX.DirectWrite; |
||||
|
using FontFamily = Avalonia.Media.FontFamily; |
||||
|
using FontStyle = SharpDX.DirectWrite.FontStyle; |
||||
|
using FontWeight = SharpDX.DirectWrite.FontWeight; |
||||
|
|
||||
namespace Avalonia.Direct2D1.Media |
namespace Avalonia.Direct2D1.Media |
||||
{ |
{ |
||||
internal static class Direct2D1FontCollectionCache |
internal static class Direct2D1FontCollectionCache |
||||
{ |
{ |
||||
private static readonly ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection> s_cachedCollections; |
private static readonly ConcurrentDictionary<FontFamilyKey, FontCollection> s_cachedCollections; |
||||
internal static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; |
internal static readonly FontCollection InstalledFontCollection; |
||||
|
|
||||
static Direct2D1FontCollectionCache() |
static Direct2D1FontCollectionCache() |
||||
{ |
{ |
||||
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection>(); |
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, FontCollection>(); |
||||
|
|
||||
s_installedFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false); |
InstalledFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false); |
||||
} |
} |
||||
|
|
||||
public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface) |
public static Font GetFont(Typeface typeface) |
||||
{ |
{ |
||||
var fontFamily = typeface.FontFamily; |
var fontFamily = typeface.FontFamily; |
||||
var fontCollection = GetOrAddFontCollection(fontFamily); |
var fontCollection = GetOrAddFontCollection(fontFamily); |
||||
var fontFamilyName = FontFamily.Default.Name; |
|
||||
|
|
||||
// Should this be cached?
|
|
||||
foreach (var familyName in fontFamily.FamilyNames) |
foreach (var familyName in fontFamily.FamilyNames) |
||||
{ |
{ |
||||
if (!fontCollection.FindFamilyName(familyName, out _)) |
if (fontCollection.FindFamilyName(familyName, out var index)) |
||||
{ |
{ |
||||
continue; |
return fontCollection.GetFontFamily(index).GetFirstMatchingFont( |
||||
|
(FontWeight)typeface.Weight, |
||||
|
FontStretch.Normal, |
||||
|
(FontStyle)typeface.Style); |
||||
} |
} |
||||
|
|
||||
fontFamilyName = familyName; |
|
||||
|
|
||||
break; |
|
||||
} |
} |
||||
|
|
||||
return new SharpDX.DirectWrite.TextFormat( |
InstalledFontCollection.FindFamilyName(FontFamily.Default.Name, out var i); |
||||
Direct2D1Platform.DirectWriteFactory, |
|
||||
fontFamilyName, |
return InstalledFontCollection.GetFontFamily(i).GetFirstMatchingFont( |
||||
fontCollection, |
(FontWeight)typeface.Weight, |
||||
(SharpDX.DirectWrite.FontWeight)typeface.Weight, |
FontStretch.Normal, |
||||
(SharpDX.DirectWrite.FontStyle)typeface.Style, |
(FontStyle)typeface.Style); |
||||
SharpDX.DirectWrite.FontStretch.Normal, |
|
||||
(float)typeface.FontSize); |
|
||||
} |
} |
||||
|
|
||||
private static SharpDX.DirectWrite.FontCollection GetOrAddFontCollection(FontFamily fontFamily) |
private static FontCollection GetOrAddFontCollection(FontFamily fontFamily) |
||||
{ |
{ |
||||
return fontFamily.Key == null ? s_installedFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection); |
return fontFamily.Key == null ? InstalledFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection); |
||||
} |
} |
||||
|
|
||||
private static SharpDX.DirectWrite.FontCollection CreateFontCollection(FontFamilyKey key) |
private static FontCollection CreateFontCollection(FontFamilyKey key) |
||||
{ |
{ |
||||
var assets = FontFamilyLoader.LoadFontAssets(key); |
var assets = FontFamilyLoader.LoadFontAssets(key); |
||||
|
|
||||
var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets); |
var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets); |
||||
|
|
||||
return new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); |
return new FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,80 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using SharpDX.DirectWrite; |
||||
|
using FontFamily = Avalonia.Media.FontFamily; |
||||
|
using FontStyle = Avalonia.Media.FontStyle; |
||||
|
using FontWeight = Avalonia.Media.FontWeight; |
||||
|
|
||||
|
namespace Avalonia.Direct2D1.Media |
||||
|
{ |
||||
|
internal class FontManagerImpl : IFontManagerImpl |
||||
|
{ |
||||
|
private readonly ConcurrentDictionary<Typeface, GlyphTypefaceImpl> _glyphTypefaceCache = |
||||
|
new ConcurrentDictionary<Typeface, GlyphTypefaceImpl>(); |
||||
|
|
||||
|
public FontManagerImpl() |
||||
|
{ |
||||
|
//ToDo: Implement a real lookup of the system's default font.
|
||||
|
DefaultFontFamilyName = "segoe ui"; |
||||
|
} |
||||
|
|
||||
|
public string DefaultFontFamilyName { get; } |
||||
|
|
||||
|
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) |
||||
|
{ |
||||
|
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount; |
||||
|
|
||||
|
var fontFamilies = new string[familyCount]; |
||||
|
|
||||
|
for (var i = 0; i < familyCount; i++) |
||||
|
{ |
||||
|
fontFamilies[i] = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i).FamilyNames.GetString(0); |
||||
|
} |
||||
|
|
||||
|
return fontFamilies; |
||||
|
} |
||||
|
|
||||
|
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) |
||||
|
{ |
||||
|
return _glyphTypefaceCache.GetOrAdd(typeface, new GlyphTypefaceImpl(typeface)); |
||||
|
} |
||||
|
|
||||
|
public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) |
||||
|
{ |
||||
|
//ToDo: Implement caching.
|
||||
|
return new Typeface(fontFamily, fontWeight, fontStyle); |
||||
|
} |
||||
|
|
||||
|
public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null) |
||||
|
{ |
||||
|
var fontFamilyName = FontFamily.Default.Name; |
||||
|
|
||||
|
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount; |
||||
|
|
||||
|
for (var i = 0; i < familyCount; i++) |
||||
|
{ |
||||
|
var font = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i) |
||||
|
.GetMatchingFonts((SharpDX.DirectWrite.FontWeight)fontWeight, FontStretch.Normal, |
||||
|
(SharpDX.DirectWrite.FontStyle)fontStyle).GetFont(0); |
||||
|
|
||||
|
if (!font.HasCharacter(codepoint)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
fontFamilyName = font.FontFamily.FamilyNames.GetString(0); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return GetTypeface(new FontFamily(fontFamilyName), fontWeight, fontStyle); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,188 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using HarfBuzzSharp; |
||||
|
using SharpDX.DirectWrite; |
||||
|
|
||||
|
namespace Avalonia.Direct2D1.Media |
||||
|
{ |
||||
|
internal class GlyphTypefaceImpl : IGlyphTypefaceImpl |
||||
|
{ |
||||
|
private bool _isDisposed; |
||||
|
|
||||
|
public GlyphTypefaceImpl(Typeface typeface) |
||||
|
{ |
||||
|
DWFont = Direct2D1FontCollectionCache.GetFont(typeface); |
||||
|
|
||||
|
FontFace = new FontFace(DWFont); |
||||
|
|
||||
|
Face = new Face(GetTable); |
||||
|
|
||||
|
Font = new HarfBuzzSharp.Font(Face); |
||||
|
|
||||
|
Font.SetFunctionsOpenType(); |
||||
|
|
||||
|
Font.GetScale(out var xScale, out _); |
||||
|
|
||||
|
DesignEmHeight = (short)xScale; |
||||
|
|
||||
|
if (!Font.TryGetHorizontalFontExtents(out var fontExtents)) |
||||
|
{ |
||||
|
Font.TryGetVerticalFontExtents(out fontExtents); |
||||
|
} |
||||
|
|
||||
|
Ascent = -fontExtents.Ascender; |
||||
|
|
||||
|
Descent = -fontExtents.Descender; |
||||
|
|
||||
|
LineGap = fontExtents.LineGap; |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition)) |
||||
|
{ |
||||
|
UnderlinePosition = underlinePosition; |
||||
|
} |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness)) |
||||
|
{ |
||||
|
UnderlineThickness = underlineThickness; |
||||
|
} |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition)) |
||||
|
{ |
||||
|
StrikethroughPosition = strikethroughPosition; |
||||
|
} |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness)) |
||||
|
{ |
||||
|
StrikethroughThickness = strikethroughThickness; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Blob GetTable(Face face, Tag tag) |
||||
|
{ |
||||
|
var dwTag = (int)SwapBytes(tag); |
||||
|
|
||||
|
if (FontFace.TryGetFontTable(dwTag, out var tableData, out _)) |
||||
|
{ |
||||
|
return new Blob(tableData.Pointer, tableData.Size, MemoryMode.ReadOnly, () => { }); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private static uint SwapBytes(uint x) |
||||
|
{ |
||||
|
x = (x >> 16) | (x << 16); |
||||
|
|
||||
|
return ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8); |
||||
|
} |
||||
|
|
||||
|
public SharpDX.DirectWrite.Font DWFont { get; } |
||||
|
|
||||
|
public FontFace FontFace { get; } |
||||
|
|
||||
|
public Face Face { get; } |
||||
|
|
||||
|
public HarfBuzzSharp.Font Font { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public short DesignEmHeight { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int Ascent { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int Descent { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int LineGap { get; } |
||||
|
|
||||
|
//ToDo: Read font table for these values
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int UnderlinePosition { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int UnderlineThickness { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int StrikethroughPosition { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int StrikethroughThickness { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public ushort GetGlyph(uint codepoint) |
||||
|
{ |
||||
|
if (Font.TryGetGlyph(codepoint, out var glyph)) |
||||
|
{ |
||||
|
return (ushort)glyph; |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) |
||||
|
{ |
||||
|
var glyphs = new ushort[codepoints.Length]; |
||||
|
|
||||
|
for (var i = 0; i < codepoints.Length; i++) |
||||
|
{ |
||||
|
if (Font.TryGetGlyph(codepoints[i], out var glyph)) |
||||
|
{ |
||||
|
glyphs[i] = (ushort)glyph; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return glyphs; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int GetGlyphAdvance(ushort glyph) |
||||
|
{ |
||||
|
return Font.GetHorizontalGlyphAdvance(glyph); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) |
||||
|
{ |
||||
|
var glyphIndices = new uint[glyphs.Length]; |
||||
|
|
||||
|
for (var i = 0; i < glyphs.Length; i++) |
||||
|
{ |
||||
|
glyphIndices[i] = glyphs[i]; |
||||
|
} |
||||
|
|
||||
|
return Font.GetHorizontalGlyphAdvances(glyphIndices); |
||||
|
} |
||||
|
|
||||
|
private void Dispose(bool disposing) |
||||
|
{ |
||||
|
if (_isDisposed) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_isDisposed = true; |
||||
|
|
||||
|
if (!disposing) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Font?.Dispose(); |
||||
|
Face?.Dispose(); |
||||
|
FontFace?.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
Dispose(true); |
||||
|
GC.SuppressFinalize(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
Loading…
Reference in new issue