1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Diagnostics; 6 using System.Diagnostics.CodeAnalysis; 7 using System.Drawing.Text; 8 using System.Globalization; 9 using System.Runtime.InteropServices; 10 using System.Text; 11 12 namespace System.Drawing 13 { 14 /// <summary> 15 /// Abstracts a group of type faces having a similar basic design but having certain variation in styles. 16 /// </summary> 17 public sealed class FontFamily : MarshalByRefObject, IDisposable 18 { 19 private const int NeutralLanguage = 0; 20 private IntPtr _nativeFamily; 21 private bool _createDefaultOnFail; 22 23 #if DEBUG 24 private static object s_lockObj = new object(); 25 private static int s_idCount = 0; 26 private int _id; 27 #endif 28 29 [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts")] SetNativeFamily(IntPtr family)30 private void SetNativeFamily(IntPtr family) 31 { 32 Debug.Assert(_nativeFamily == IntPtr.Zero, "Setting GDI+ native font family when already initialized."); 33 34 _nativeFamily = family; 35 #if DEBUG 36 lock (s_lockObj) 37 { 38 _id = ++s_idCount; 39 } 40 #endif 41 } 42 43 internal FontFamily(IntPtr family) => SetNativeFamily(family); 44 45 /// <summary> 46 /// Initializes a new instance of the <see cref='FontFamily'/> class with the specified name. 47 /// 48 /// The <paramref name="createDefaultOnFail"/> parameter determines how errors are handled when creating a 49 /// font based on a font family that does not exist on the end user's system at run time. If this parameter is 50 /// true, then a fall-back font will always be used instead. If this parameter is false, an exception will be thrown. 51 /// </summary> FontFamily(string name, bool createDefaultOnFail)52 internal FontFamily(string name, bool createDefaultOnFail) 53 { 54 _createDefaultOnFail = createDefaultOnFail; 55 CreateFontFamily(name, null); 56 } 57 58 /// <summary> 59 /// Initializes a new instance of the <see cref='FontFamily'/> class with the specified name. 60 /// </summary> FontFamily(string name)61 public FontFamily(string name) => CreateFontFamily(name, null); 62 63 /// <summary> 64 /// Initializes a new instance of the <see cref='FontFamily'/> class in the specified 65 /// <see cref='FontCollection'/> and with the specified name. 66 /// </summary> FontFamily(string name, FontCollection fontCollection)67 public FontFamily(string name, FontCollection fontCollection) => CreateFontFamily(name, fontCollection); 68 69 // Creates the native font family object. 70 // Note: GDI+ creates singleton font family objects (from the corresponding font file) and reference count them. CreateFontFamily(string name, FontCollection fontCollection)71 private void CreateFontFamily(string name, FontCollection fontCollection) 72 { 73 IntPtr fontfamily = IntPtr.Zero; 74 IntPtr nativeFontCollection = (fontCollection == null) ? IntPtr.Zero : fontCollection._nativeFontCollection; 75 76 int status = SafeNativeMethods.Gdip.GdipCreateFontFamilyFromName(name, new HandleRef(fontCollection, nativeFontCollection), out fontfamily); 77 78 if (status != SafeNativeMethods.Gdip.Ok) 79 { 80 if (_createDefaultOnFail) 81 { 82 fontfamily = GetGdipGenericSansSerif(); // This throws if failed. 83 } 84 else 85 { 86 // Special case this incredibly common error message to give more information. 87 if (status == SafeNativeMethods.Gdip.FontFamilyNotFound) 88 { 89 throw new ArgumentException(SR.Format(SR.GdiplusFontFamilyNotFound, name)); 90 } 91 else if (status == SafeNativeMethods.Gdip.NotTrueTypeFont) 92 { 93 throw new ArgumentException(SR.Format(SR.GdiplusNotTrueTypeFont, name)); 94 } 95 else 96 { 97 throw SafeNativeMethods.Gdip.StatusException(status); 98 } 99 } 100 } 101 102 SetNativeFamily(fontfamily); 103 } 104 105 /// <summary> 106 /// Initializes a new instance of the <see cref='FontFamily'/> class from the specified generic font family. 107 /// </summary> FontFamily(GenericFontFamilies genericFamily)108 public FontFamily(GenericFontFamilies genericFamily) 109 { 110 IntPtr nativeFamily = IntPtr.Zero; 111 int status; 112 113 switch (genericFamily) 114 { 115 case GenericFontFamilies.Serif: 116 status = SafeNativeMethods.Gdip.GdipGetGenericFontFamilySerif(out nativeFamily); 117 break; 118 case GenericFontFamilies.SansSerif: 119 status = SafeNativeMethods.Gdip.GdipGetGenericFontFamilySansSerif(out nativeFamily); 120 break; 121 case GenericFontFamilies.Monospace: 122 default: 123 status = SafeNativeMethods.Gdip.GdipGetGenericFontFamilyMonospace(out nativeFamily); 124 break; 125 } 126 SafeNativeMethods.Gdip.CheckStatus(status); 127 128 SetNativeFamily(nativeFamily); 129 } 130 ~FontFamily()131 ~FontFamily() => Dispose(false); 132 133 internal IntPtr NativeFamily => _nativeFamily; 134 Equals(object obj)135 public override bool Equals(object obj) 136 { 137 if (obj == this) 138 { 139 return true; 140 } 141 142 if (!(obj is FontFamily otherFamily)) 143 { 144 return false; 145 } 146 147 // We can safely use the ptr to the native GDI+ FontFamily because it is common to 148 // all objects of the same family (singleton RO object). 149 return otherFamily.NativeFamily == NativeFamily; 150 } 151 152 /// <summary> 153 /// Converts this <see cref='FontFamily'/> to a human-readable string. 154 /// </summary> ToString()155 public override string ToString() => $"[{GetType().Name}: Name={Name}]"; 156 157 /// <summary> 158 /// Gets a hash code for this <see cref='FontFamily'/>. 159 /// </summary> GetHashCode()160 public override int GetHashCode() => GetName(NeutralLanguage).GetHashCode(); 161 162 private static int CurrentLanguage => CultureInfo.CurrentUICulture.LCID; 163 164 /// <summary> 165 /// Disposes of this <see cref='FontFamily'/>. 166 /// </summary> Dispose()167 public void Dispose() 168 { 169 Dispose(true); 170 GC.SuppressFinalize(this); 171 } 172 Dispose(bool disposing)173 private void Dispose(bool disposing) 174 { 175 if (_nativeFamily != IntPtr.Zero) 176 { 177 try 178 { 179 #if DEBUG 180 int status = 181 #endif 182 SafeNativeMethods.Gdip.GdipDeleteFontFamily(new HandleRef(this, _nativeFamily)); 183 #if DEBUG 184 Debug.Assert(status == SafeNativeMethods.Gdip.Ok, "GDI+ returned an error status: " + status.ToString(CultureInfo.InvariantCulture)); 185 #endif 186 } 187 catch (Exception ex) when (!ClientUtils.IsCriticalException(ex)) 188 { 189 } 190 finally 191 { 192 _nativeFamily = IntPtr.Zero; 193 } 194 } 195 } 196 197 /// <summary> 198 /// Gets the name of this <see cref='FontFamily'/>. 199 /// </summary> 200 public string Name => GetName(CurrentLanguage); 201 202 /// <summary> 203 /// Returns the name of this <see cref='FontFamily'/> in the specified language. 204 /// </summary> GetName(int language)205 public string GetName(int language) 206 { 207 // LF_FACESIZE is 32 208 var name = new StringBuilder(32); 209 210 int status = SafeNativeMethods.Gdip.GdipGetFamilyName(new HandleRef(this, NativeFamily), name, language); 211 SafeNativeMethods.Gdip.CheckStatus(status); 212 213 return name.ToString(); 214 } 215 216 /// <summary> 217 /// Returns an array that contains all of the <see cref='FontFamily'/> objects associated with the current 218 /// graphics context. 219 /// </summary> 220 public static FontFamily[] Families => new InstalledFontCollection().Families; 221 222 /// <summary> 223 /// Gets a generic SansSerif <see cref='FontFamily'/>. 224 /// </summary> 225 public static FontFamily GenericSansSerif => new FontFamily(GetGdipGenericSansSerif()); 226 GetGdipGenericSansSerif()227 private static IntPtr GetGdipGenericSansSerif() 228 { 229 IntPtr nativeFamily = IntPtr.Zero; 230 int status = SafeNativeMethods.Gdip.GdipGetGenericFontFamilySansSerif(out nativeFamily); 231 SafeNativeMethods.Gdip.CheckStatus(status); 232 233 return nativeFamily; 234 } 235 236 /// <summary> 237 /// Gets a generic Serif <see cref='FontFamily'/>. 238 /// </summary> 239 public static FontFamily GenericSerif => new FontFamily(GenericFontFamilies.Serif); 240 241 /// <summary> 242 /// Gets a generic monospace <see cref='FontFamily'/>. 243 /// </summary> 244 public static FontFamily GenericMonospace => new FontFamily(GenericFontFamilies.Monospace); 245 246 /// <summary> 247 /// Returns an array that contains all of the <see cref='FontFamily'/> objects associated with the specified 248 /// graphics context. 249 [Obsolete("Do not use method GetFamilies, use property Families instead")] GetFamilies(Graphics graphics)250 public static FontFamily[] GetFamilies(Graphics graphics) 251 { 252 if (graphics == null) 253 { 254 throw new ArgumentNullException(nameof(graphics)); 255 } 256 257 return new InstalledFontCollection().Families; 258 } 259 260 /// <summary> 261 /// Indicates whether the specified <see cref='FontStyle'/> is available. 262 /// </summary> IsStyleAvailable(FontStyle style)263 public bool IsStyleAvailable(FontStyle style) 264 { 265 int bresult; 266 int status = SafeNativeMethods.Gdip.GdipIsStyleAvailable(new HandleRef(this, NativeFamily), style, out bresult); 267 SafeNativeMethods.Gdip.CheckStatus(status); 268 269 return bresult != 0; 270 } 271 272 /// <summary> 273 /// Gets the size of the Em square for the specified style in font design units. 274 /// </summary> GetEmHeight(FontStyle style)275 public int GetEmHeight(FontStyle style) 276 { 277 int result = 0; 278 int status = SafeNativeMethods.Gdip.GdipGetEmHeight(new HandleRef(this, NativeFamily), style, out result); 279 SafeNativeMethods.Gdip.CheckStatus(status); 280 281 return result; 282 } 283 284 /// <summary> 285 /// Returns the ascender metric for Windows. 286 /// </summary> GetCellAscent(FontStyle style)287 public int GetCellAscent(FontStyle style) 288 { 289 int result = 0; 290 int status = SafeNativeMethods.Gdip.GdipGetCellAscent(new HandleRef(this, NativeFamily), style, out result); 291 SafeNativeMethods.Gdip.CheckStatus(status); 292 293 return result; 294 } 295 296 /// <summary> 297 /// Returns the descender metric for Windows. 298 /// </summary> GetCellDescent(FontStyle style)299 public int GetCellDescent(FontStyle style) 300 { 301 int result = 0; 302 int status = SafeNativeMethods.Gdip.GdipGetCellDescent(new HandleRef(this, NativeFamily), style, out result); 303 SafeNativeMethods.Gdip.CheckStatus(status); 304 305 return result; 306 } 307 308 /// <summary> 309 /// Returns the distance between two consecutive lines of text for this <see cref='FontFamily'/> with the 310 /// specified <see cref='FontStyle'/>. 311 /// </summary> GetLineSpacing(FontStyle style)312 public int GetLineSpacing(FontStyle style) 313 { 314 int result = 0; 315 int status = SafeNativeMethods.Gdip.GdipGetLineSpacing(new HandleRef(this, NativeFamily), style, out result); 316 SafeNativeMethods.Gdip.CheckStatus(status); 317 318 return result; 319 } 320 } 321 } 322