| File: System\Drawing\FontFamily.cs | Web Access |
| Project: src\winforms\src\System.Drawing.Common\src\System.Drawing.Common.csproj (System.Drawing.Common) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing.Text; using System.Globalization; namespace System.Drawing; /// <summary> /// Abstracts a group of type faces having a similar basic design but having certain variation in styles. /// </summary> public sealed unsafe class FontFamily : MarshalByRefObject, IDisposable, IPointer<GpFontFamily> { private const ushort NeutralLanguage = 0; private GpFontFamily* _nativeFamily; private bool _fromInstalledFontCollection; nint IPointer<GpFontFamily>.Pointer => (nint)_nativeFamily; private void SetNativeFamily(GpFontFamily* family) { Debug.Assert(_nativeFamily is null, "Setting GDI+ native font family when already initialized."); _nativeFamily = family; } internal FontFamily(GpFontFamily* family, bool fromInstalledFontCollection) { _fromInstalledFontCollection = fromInstalledFontCollection; if (fromInstalledFontCollection) { // No need to clean up FontFamily objects from the installed font collection. GC.SuppressFinalize(this); } else { GpFontFamily* clonedFamily; PInvokeGdiPlus.GdipCloneFontFamily(family, &clonedFamily).ThrowIfFailed(); // Only the font collection is ref counted, new font family instances are not created. Debug.Assert(clonedFamily == family); family = clonedFamily; } SetNativeFamily(family); } internal FontFamily Clone() { if (_fromInstalledFontCollection) { // No need to copy, we're never going to dispose of the native object. return this; } return new(_nativeFamily, fromInstalledFontCollection: false); } /// <summary> /// Initializes a new instance of the <see cref='FontFamily'/> class with the specified name. /// </summary> /// <param name="createDefaultOnFail"> /// Determines how errors are handled when creating a font based on a font family that does not exist on the end /// user's system at run time. If this parameter is true, then a fall-back font will always be used instead. /// If this parameter is false, an exception will be thrown. /// </param> internal FontFamily(ReadOnlySpan<char> name, bool createDefaultOnFail) => CreateFontFamily(name, fontCollection: null, createDefaultOnFail); /// <summary> /// Initializes a new instance of the <see cref='FontFamily'/> class with the specified name. /// </summary> public FontFamily(string name) => CreateFontFamily(name.OrThrowIfNull(), fontCollection: null); /// <summary> /// Initializes a new instance of the <see cref='FontFamily'/> class in the specified /// <see cref='FontCollection'/> and with the specified name. /// </summary> public FontFamily(string name, FontCollection? fontCollection) => CreateFontFamily(name.OrThrowIfNull(), fontCollection); // Creates the native font family object. // Note: GDI+ creates singleton font family objects (from the corresponding font file) and reference count them. private void CreateFontFamily(ReadOnlySpan<char> name, FontCollection? fontCollection, bool createDefaultOnFail = false) { GpFontFamily* fontFamily; GpFontCollection* nativeFontCollection = fontCollection.Pointer(); _fromInstalledFontCollection = nativeFontCollection is null || nativeFontCollection == InstalledFontCollection.Instance.Pointer(); Status status = Status.Ok; fixed (char* n = name) { Debug.Assert(n is null || n[name.Length] == '\0', "Expected null-terminated string."); status = PInvokeGdiPlus.GdipCreateFontFamilyFromName(n, nativeFontCollection, &fontFamily); } if (status != Status.Ok) { if (createDefaultOnFail) { fontFamily = GetGdipGenericSansSerif(); } else { // Special case this incredibly common error message to give more information. if (status == Status.FontFamilyNotFound) { throw new ArgumentException(SR.Format(SR.GdiplusFontFamilyNotFound, name.ToString())); } else if (status == Status.NotTrueTypeFont) { throw new ArgumentException(SR.Format(SR.GdiplusNotTrueTypeFont, name.ToString())); } else { status.ThrowIfFailed(); } } } if (_fromInstalledFontCollection) { // No need to clean up FontFamily objects from the installed font collection. GC.SuppressFinalize(this); } GC.KeepAlive(fontCollection); SetNativeFamily(fontFamily); } /// <summary> /// Initializes a new instance of the <see cref='FontFamily'/> class from the specified generic font family. /// </summary> public FontFamily(GenericFontFamilies genericFamily) { GpFontFamily* nativeFamily; switch (genericFamily) { case GenericFontFamilies.Serif: PInvokeGdiPlus.GdipGetGenericFontFamilySerif(&nativeFamily).ThrowIfFailed(); break; case GenericFontFamilies.SansSerif: PInvokeGdiPlus.GdipGetGenericFontFamilySansSerif(&nativeFamily).ThrowIfFailed(); break; case GenericFontFamilies.Monospace: default: PInvokeGdiPlus.GdipGetGenericFontFamilyMonospace(&nativeFamily).ThrowIfFailed(); break; } _fromInstalledFontCollection = true; SetNativeFamily(nativeFamily); } ~FontFamily() => Dispose(disposing: false); internal GpFontFamily* NativeFamily => _nativeFamily; /// <summary> /// Converts this <see cref='FontFamily'/> to a human-readable string. /// </summary> public override string ToString() => $"[{nameof(FontFamily)}: Name={Name}]"; public override bool Equals([NotNullWhen(true)] object? obj) { if (obj == this) { return true; } if (obj is not FontFamily otherFamily) { return false; } // GDI+ font families are instances in their font collection, so we can compare the pointers. return otherFamily.NativeFamily == NativeFamily; } /// <summary> /// Gets a hash code for this <see cref='FontFamily'/>. /// </summary> public override int GetHashCode() { Span<char> name = stackalloc char[(int)PInvokeCore.LF_FACESIZE]; GetName(name, NeutralLanguage); return string.GetHashCode(name.SliceAtFirstNull()); } /// <summary> /// Disposes of this <see cref='FontFamily'/>. /// </summary> public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (_nativeFamily is null || _fromInstalledFontCollection) { return; } // This will ref count the associated FontCollection object. This is only strictly necessary for // manually loaded FontCollections (PrivateFontCollection). Status status = PInvokeGdiPlus.GdipDeleteFontFamily(_nativeFamily); _nativeFamily = null; if (disposing) { Debug.Assert(status == Status.Ok, $"GDI+ returned an error status: {status}"); } } /// <summary> /// Gets the name of this <see cref='FontFamily'/>. /// </summary> public string Name => GetName(CultureInfo.CurrentUICulture.LCID); /// <summary> /// Returns the name of this <see cref='FontFamily'/> in the specified language. /// </summary> public unsafe string GetName(int language) { Span<char> name = stackalloc char[(int)PInvokeCore.LF_FACESIZE]; GetName(name, (ushort)language); return new(name.SliceAtFirstNull()); } private unsafe void GetName(Span<char> span, ushort language) { Debug.Assert(span.Length == (int)PInvokeCore.LF_FACESIZE); fixed (char* name = span) { PInvokeGdiPlus.GdipGetFamilyName(NativeFamily, name, language).ThrowIfFailed(); } } /// <summary> /// Returns an array that contains all of the <see cref='FontFamily'/> objects associated with the current /// graphics context. /// </summary> public static FontFamily[] Families => InstalledFontCollection.Instance.Families; /// <summary> /// Gets a generic SansSerif <see cref='FontFamily'/>. /// </summary> public static FontFamily GenericSansSerif => new(GetGdipGenericSansSerif(), fromInstalledFontCollection: true); private static GpFontFamily* GetGdipGenericSansSerif() { GpFontFamily* nativeFamily; PInvokeGdiPlus.GdipGetGenericFontFamilySansSerif(&nativeFamily).ThrowIfFailed(); return nativeFamily; } /// <summary> /// Gets a generic Serif <see cref='FontFamily'/>. /// </summary> public static FontFamily GenericSerif => new(GenericFontFamilies.Serif); /// <summary> /// Gets a generic monospace <see cref='FontFamily'/>. /// </summary> public static FontFamily GenericMonospace => new(GenericFontFamilies.Monospace); /// <summary> /// Returns an array that contains all of the <see cref='FontFamily'/> objects associated with the specified /// graphics context. /// </summary> [Obsolete("FontFamily.GetFamilies has been deprecated. Use Families instead.")] public static FontFamily[] GetFamilies(Graphics graphics) { ArgumentNullException.ThrowIfNull(graphics); return InstalledFontCollection.Instance.Families; } /// <summary> /// Indicates whether the specified <see cref='FontStyle'/> is available. /// </summary> public bool IsStyleAvailable(FontStyle style) { BOOL isStyleAvailable; PInvokeGdiPlus.GdipIsStyleAvailable(NativeFamily, (int)style, &isStyleAvailable).ThrowIfFailed(); GC.KeepAlive(this); return isStyleAvailable; } /// <summary> /// Gets the size of the Em square for the specified style in font design units. /// </summary> public int GetEmHeight(FontStyle style) { ushort emHeight; PInvokeGdiPlus.GdipGetEmHeight(NativeFamily, (int)style, &emHeight).ThrowIfFailed(); GC.KeepAlive(this); return emHeight; } /// <summary> /// Returns the ascender metric for Windows. /// </summary> public int GetCellAscent(FontStyle style) { ushort cellAscent; PInvokeGdiPlus.GdipGetCellAscent(NativeFamily, (int)style, &cellAscent).ThrowIfFailed(); GC.KeepAlive(this); return cellAscent; } /// <summary> /// Returns the descender metric for Windows. /// </summary> public int GetCellDescent(FontStyle style) { ushort cellDescent; PInvokeGdiPlus.GdipGetCellDescent(NativeFamily, (int)style, &cellDescent).ThrowIfFailed(); GC.KeepAlive(this); return cellDescent; } /// <summary> /// Returns the distance between two consecutive lines of text for this <see cref='FontFamily'/> with the /// specified <see cref='FontStyle'/>. /// </summary> public int GetLineSpacing(FontStyle style) { ushort lineSpacing; PInvokeGdiPlus.GdipGetLineSpacing(NativeFamily, (int)style, &lineSpacing).ThrowIfFailed(); GC.KeepAlive(this); return lineSpacing; } }