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.IO;
7 using System.Runtime.InteropServices;
8 
9 namespace System.Drawing
10 {
11     public static partial class SystemFonts
12     {
13         public static Font CaptionFont
14         {
15             get
16             {
17                 Font captionFont = null;
18 
19                 var data = new NativeMethods.NONCLIENTMETRICS();
20                 bool result = UnsafeNativeMethods.SystemParametersInfo(NativeMethods.SPI_GETNONCLIENTMETRICS, data.cbSize, data, 0);
21 
22                 if (result)
23                 {
24                     captionFont = GetFontFromData(data.lfCaptionFont);
25                 }
26 
27                 captionFont.SetSystemFontName(nameof(CaptionFont));
28                 return captionFont;
29             }
30         }
31 
32         public static Font SmallCaptionFont
33         {
34             get
35             {
36                 Font smcaptionFont = null;
37 
38                 var data = new NativeMethods.NONCLIENTMETRICS();
39                 bool result = UnsafeNativeMethods.SystemParametersInfo(NativeMethods.SPI_GETNONCLIENTMETRICS, data.cbSize, data, 0);
40 
41                 if (result)
42                 {
43                     smcaptionFont = GetFontFromData(data.lfSmCaptionFont);
44                 }
45 
46                 smcaptionFont.SetSystemFontName(nameof(SmallCaptionFont));
47                 return smcaptionFont;
48             }
49         }
50 
51         public static Font MenuFont
52         {
53             get
54             {
55                 Font menuFont = null;
56 
57                 var data = new NativeMethods.NONCLIENTMETRICS();
58                 bool result = UnsafeNativeMethods.SystemParametersInfo(NativeMethods.SPI_GETNONCLIENTMETRICS, data.cbSize, data, 0);
59 
60                 if (result)
61                 {
62                     menuFont = GetFontFromData(data.lfMenuFont);
63                 }
64 
65                 menuFont.SetSystemFontName(nameof(MenuFont));
66                 return menuFont;
67             }
68         }
69 
70         public static Font StatusFont
71         {
72             get
73             {
74                 Font statusFont = null;
75 
76                 var data = new NativeMethods.NONCLIENTMETRICS();
77                 bool result = UnsafeNativeMethods.SystemParametersInfo(NativeMethods.SPI_GETNONCLIENTMETRICS, data.cbSize, data, 0);
78 
79                 if (result)
80                 {
81                     statusFont = GetFontFromData(data.lfStatusFont);
82                 }
83 
84                 statusFont.SetSystemFontName(nameof(StatusFont));
85                 return statusFont;
86             }
87         }
88 
89         public static Font MessageBoxFont
90         {
91             get
92             {
93                 Font messageBoxFont = null;
94 
95                 var data = new NativeMethods.NONCLIENTMETRICS();
96                 bool result = UnsafeNativeMethods.SystemParametersInfo(NativeMethods.SPI_GETNONCLIENTMETRICS, data.cbSize, data, 0);
97 
98                 if (result)
99                 {
100                     messageBoxFont = GetFontFromData(data.lfMessageFont);
101                 }
102 
103                 messageBoxFont.SetSystemFontName(nameof(MessageBoxFont));
104                 return messageBoxFont;
105             }
106         }
107 
IsCriticalFontException(Exception ex)108         private static bool IsCriticalFontException(Exception ex)
109         {
110             return !(
111                 // In any of these cases we'll handle the exception.
112                 ex is ExternalException ||
113                 ex is ArgumentException ||
114                 ex is OutOfMemoryException || // GDI+ throws this one for many reasons other than actual OOM.
115                 ex is InvalidOperationException ||
116                 ex is NotImplementedException ||
117                 ex is FileNotFoundException);
118         }
119 
120         public static Font IconTitleFont
121         {
122             get
123             {
124                 Font iconTitleFont = null;
125 
126                 var itfont = new SafeNativeMethods.LOGFONT();
127                 bool result = UnsafeNativeMethods.SystemParametersInfo(NativeMethods.SPI_GETICONTITLELOGFONT, Marshal.SizeOf(itfont), itfont, 0);
128 
129                 if (result)
130                 {
131                     iconTitleFont = GetFontFromData(itfont);
132                 }
133 
134                 iconTitleFont.SetSystemFontName(nameof(IconTitleFont));
135                 return iconTitleFont;
136             }
137         }
138 
139         public static Font DefaultFont
140         {
141             get
142             {
143                 Font defaultFont = null;
144 
145                 // For Arabic systems, always return Tahoma 8.
146                 bool systemDefaultLCIDIsArabic = (UnsafeNativeMethods.GetSystemDefaultLCID() & 0x3ff) == 0x0001;
147                 if (systemDefaultLCIDIsArabic)
148                 {
149                     try
150                     {
151                         defaultFont = new Font("Tahoma", 8);
152                     }
153                     catch (Exception ex) when (!IsCriticalFontException(ex)) { }
154                 }
155 
156                 // First try DEFAULT_GUI.
157                 if (defaultFont == null)
158                 {
159                     IntPtr handle = UnsafeNativeMethods.GetStockObject(NativeMethods.DEFAULT_GUI_FONT);
160                     try
161                     {
162                         using (Font fontInWorldUnits = Font.FromHfont(handle))
163                         {
164                             defaultFont = FontInPoints(fontInWorldUnits);
165                         }
166                     }
167                     catch (ArgumentException)
168                     {
169                     }
170                 }
171 
172                 // If DEFAULT_GUI didn't work, try Tahoma.
173                 if (defaultFont == null)
174                 {
175                     try
176                     {
177                         defaultFont = new Font("Tahoma", 8);
178                     }
179                     catch (ArgumentException)
180                     {
181                     }
182                 }
183 
184                 // Use GenericSansSerif as a last resort - this will always work.
185                 if (defaultFont == null)
186                 {
187                     defaultFont = new Font(FontFamily.GenericSansSerif, 8);
188                 }
189 
190                 if (defaultFont.Unit != GraphicsUnit.Point)
191                 {
192                     defaultFont = FontInPoints(defaultFont);
193                 }
194 
195                 Debug.Assert(defaultFont != null, "defaultFont wasn't set.");
196 
197                 defaultFont.SetSystemFontName(nameof(DefaultFont));
198                 return defaultFont;
199             }
200         }
201 
202         public static Font DialogFont
203         {
204             get
205             {
206                 Font dialogFont = null;
207 
208                 if ((UnsafeNativeMethods.GetSystemDefaultLCID() & 0x3ff) == 0x0011)
209                 {
210                     // Always return DefaultFont for Japanese cultures.
211                     dialogFont = DefaultFont;
212                 }
213                 else
214                 {
215                     try
216                     {
217                         // Use MS Shell Dlg 2, 8pt for anything other than than Japanese.
218                         dialogFont = new Font("MS Shell Dlg 2", 8);
219                     }
220                     catch (ArgumentException)
221                     {
222                     }
223                 }
224 
225                 if (dialogFont == null)
226                 {
227                     dialogFont = DefaultFont;
228                 }
229                 else if (dialogFont.Unit != GraphicsUnit.Point)
230                 {
231                     dialogFont = FontInPoints(dialogFont);
232                 }
233 
234                 // For Japanese cultures, SystemFonts.DefaultFont returns a new Font object every time it is invoked.
235                 // So for Japanese we return the DefaultFont with its SystemFontName set to DialogFont.
236                 dialogFont.SetSystemFontName(nameof(DialogFont));
237                 return dialogFont;
238             }
239         }
240 
FontInPoints(Font font)241         private static Font FontInPoints(Font font)
242         {
243             return new Font(font.FontFamily, font.SizeInPoints, font.Style, GraphicsUnit.Point, font.GdiCharSet, font.GdiVerticalFont);
244         }
245 
GetFontFromData(SafeNativeMethods.LOGFONT logFont)246         private static Font GetFontFromData(SafeNativeMethods.LOGFONT logFont)
247         {
248             if (logFont == null)
249             {
250                 return null;
251             }
252 
253             Font font = null;
254             try
255             {
256                 font = Font.FromLogFont(logFont);
257             }
258             catch (Exception ex) when (!IsCriticalFontException(ex)) { }
259 
260             return
261                 font == null ? DefaultFont :
262                 font.Unit != GraphicsUnit.Point ? FontInPoints(font) :
263                 font;
264         }
265     }
266 }
267