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