http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnaraskdr/html/askgui12302003.asp
December 30, 2003
Got a problem or a stumper question on anything to do with Internet or Windows-based development? Ask the good doctor (drgui@microsoft.com); he's here for you twice a month, online at MSDN. Though his busy surgery schedule precludes any individual responses, he'll answer as many as he can here—and if yours is picked, he'll be glad to send you an official Dr. GUI t-shirt!
Summary: Dr. GUI describes how to determine whether a font is a normal font, like Arial, or a symbol font like Wingdings that uses the Symbol character set. Dr. GUI achieves this by determining the character set used by the font in question, and also provides examples in C# and Visual Basic .NET. (10 printed pages)
Download the C# source code for this article.
Download the VB .NET source code for this article.
Dr. GUI's New Year's Resolutions (For You)
With the New Year now upon us, the good doctor often thinks back to the year past; this helps him plan for the future. There were a number of important events for his patients in 2003:
- A new version of Microsoft® Visual Studio® .NET (called, creatively enough, 2003) was released in April, along with a new version of the Microsoft® .NET Framework.
- MSDN released its Developer Centers—one stop shopping for specific topics, such as Microsoft® Visual Basic, Security or Microsoft® ASP.NET. These centers are intended to help you discover information, and to help you develop. Take a look, and give the Content Strategist a hearty "Hello" from Dr. GUI.
- A new version of Microsoft® Office (no longer just a program, now a System) found its way into many of your hands. The good doctor loves the new version (now he can mark items in his Inbox with all colors of flags, so he knows why he's ignoring them). The Office System isn't just for end users, as many of you know. It's also a powerful programming environment to help you build solutions to many tough problems. Later, the Visual Studio Tools for Office were released, enabling you to build solutions for Microsoft® Word and Microsoft® Excel using Visual Studio and the .NET Framework.
- Another PDC (Professional Developers' Conference) came and went, this time describing the wonders waiting ahead with Microsoft® Visual Studio® 2005 (the next release of Visual Studio), Microsoft® SQL Server 2005 (the next release of SQL Server) and Microsoft® Windows® code named "Longhorn" (the next release of Windows). They're all a ways out, and so it would be easy to ignore them until you actually have a project that uses them. However, it's worth taking a look today, as you can use the ideas to architect today's programs to prepare for the upgrade later.
These are only four of the events of the year for developers, but they do point to one trend: managed code is here to stay.
So, on to what Dr. GUI thinks should be one of your New Year's Resolutions: If you haven't looked at programming in managed code using the .NET Framework and Visual Studio .NET yet, take a look. Build a small application with it; solve a programming itch (until they develop programmers' Calamine Lotion). You'll see it's not that different from what you've done before. What you end up with, however, is an application that is safer (from buffer overruns or many other security problems) and more capable (as you now have the full .NET Framework class library at your fingertips) than ever before.
If you are currently working with the .NET Framework, try to broaden your horizons. If you primarily work with Microsoft® ASP.NET, try creating a "no-touch deploy" Microsoft® Windows Forms application. Alternately, if Windows Forms is your bag, take a look at ASP.NET. Or, create a Windows Service, take a look at the new Speech SDK. Learning, says Dr. GUI, is something that should go on your whole life. If you do give something new a try, drop the good doctor an e-mail message; he always loves to hear from you!
Is This a Symbol Font I See Before Me?
Dear Dr. GUI,
Is there any way to find out whether a specified Font object is a normal font (containing English chars) or a symbol font (like Wingdings)? I have tried the Font.ToLogFont method, but it will not fill in the LOGFONT structure.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Public Structure LOGFONT
Public lfHeight As Integer
Public lfWidth As Integer
Public lfEscapement As Integer
Public lfOrientation As Integer
Public lfWeight As Integer
Public lfItalic As Byte
Public lfUnderline As Byte
Public lfStrikeOut As Byte
Public lfCharSet As Byte
Public lfOutPrecision As Byte
Public lfClipPrecision As Byte
Public lfQuality As Byte
Public lfPitchAndFamily As Byte
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
Public lfFaceName As String
End Structure
Public Sub Blah()
Dim myFont As New Font("Tahoma", 8, FontStyle.Regular)
Dim myLOGFONT As LOGFONT
myFont.ToLogFont(myLOGFONT)
'myLOGFONT will not contain any data.
End Sub
Please let me know what I've done wrong.
Dr. GUI Replies:
Thanks for your question, and the associated code snippets, which make Dr. GUI's job much easier, because they provide the specific context for your question. As far as what you've asked, you can bet your bottom dollar there's a way to find out exactly what you want. Dr. GUI has diagnosed where you went wrong, and will offer his insight into this malady as well. However, before Dr. GUI jots his prescription down, there is a prescription that he needs to detail first so you'll be better able to digest his pills.
For starters, you can determine whether a font is a normal font or a symbol font by determining the character set used by the font in question. That is, the character set used by a font provides the basic template upon which the font is designed. It follows that the character set also determines whether a font is a normal font or a symbol font, or any other type of font for that matter. Now that this much is clear, let's cut straight to the chase...
Dr. GUI suggests a closer look at the System.Drawing.Font class. This class is part of the System.Drawing namespace, which provides a managed class interface that encapsulates the GDI+ APIs. You can find more details about the Font class at Font Class in the MSDN Library. Upon closer examination of this class, you'll come across a property called GdiCharSet. GdiCharSet is a byte value that specifies the character set used by a Font object, and is the answer to your question.
So, let's try out the use of GdiCharSet. For now, let's assume that you already have a reference to a Font object. In this case, all you need to do is to retrieve the value of GdiCharSet, and you're cured. Well, almost. If you look at the values for GdiCharSet for a number of fonts, all you'll get back is a number. This is because GdiCharSet is a byte value representing a particular character set. Therefore, you'll need to map this byte value to the corresponding character set, as defined in WinGDI.h. WinGDI.h is a Windows header file containing, among other things, the pre-processor directives used to define GDI constants. In order to simplify your task of mapping GdiCharSet to the corresponding character set, you can use the following table, which contains a partial list of mappings.
| Value of MyFont.GdiCharSet |
Corresponding Character Set Defined in WinGDI.h |
| 0 |
ANSI_CHARSET |
| 1 |
DEFAULT_CHARSET |
| 2 |
SYMBOL_CHARSET |
| 77 |
MAC_CHARSET |
| 128 |
SHIFTJIS_CHARSET |
| 129 |
HANGUL_CHARSET |
| 130 |
JOHAB_CHARSET |
| 134 |
GB2312_CHARSET |
| 136 |
CHINESEBIG5_CHARSET |
| 161 |
GREEK_CHARSET |
| 162 |
TURKISH_CHARSET |
| 163 |
VIETNAMESE_CHARSET |
| 177 |
HEBREW_CHARSET |
| 178 |
ARABIC_CHARSET |
| 186 |
BALTIC_CHARSET |
| 204 |
RUSSIAN_CHARSET |
| 222 |
THAI_CHARSET |
| 238 |
EASTEUROPE_CHARSET |
| 255 |
OEM_CHARSET |
What you're looking for, vis-à-vis normal font versus symbol font, depends on whether the value of GdiCharSet is 2. As the table shows, 2 corresponds to SYMBOL_CHARSET or, in other words, the Symbol character set used by symbol fonts, like Webdings or Wingdings.
Since the table provided by Dr. GUI is not exhaustive (your WinGDI.h file might contain more pre-processor directives, depending upon the character sets that your operating system supports), you might encounter a scenario where GdiCharSet contains a value that is not listed in this table. In such a case, Dr. GUI's suggestion would be to manually look up your WinGDI.h file, which is typically located at the following path (you may need to install the Platform SDK first), for the corresponding pre-processor directive.
%Program Files%\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Include
All the pre-processor directives relating to character sets are written sequentially. So, you need to start off by locating the following directive.
#define ANSI_CHARSET 0
Once you locate this directive, scan through the subsequent directives till you finally strike gold and come across an appropriate identifier defined as having the value contained in GdiCharSet. This identifier will give you details regarding the character set corresponding to GdiCharSet.
Dr. GUI now guides you through a typical scenario wherein you already have a Font object, and all you need to do is to retrieve GdiCharSet to determine whether your Font object is a normal font or a symbol font. This example should clearly explain the relationship between the character set used by your Font object, and GdiCharSet. Assume that you're working on a Windows application. Remember, any control that you use in your application will have a Font property, indicating the associated Font object.
To quickly test the relationship between the character set used by your Font object and GdiCharSet, perform the following steps:
- In Visual Studio .NET, create a Windows application using either Microsoft® Visual C#® .NET or Microsoft® Visual Basic® .NET. Note that Form1 is created by default.
- Drag a button onto Form1.
- From the Properties window for this button, change the Name property to FontCheck.
- From the Properties window for this button, change the Font property to any symbol font, like Webdings or Wingdings.
- Add the following code to the FontCheck_Click event-handler.
C# code:
MessageBox.Show(FontCheck.Font.GdiCharSet.ToString());
Visual Basic .NET code:
MessageBox.Show(FontCheck.Font.GdiCharSet)
- Build and run your application. Form1 is displayed.
- Click the FontCheck button on Form1.
You should receive a message box with 2 as the text. Try the same example with Simplified Arabic (or another font on your system) in step 4, instead of a symbol font. You should receive a message box with 178 as the text. Finally, try this example after reverting, in step 4, to any of the normal fonts that you're used to (Arial, Microsoft Sans Serif, and the like). This time, your message box should display 0.
Dr. GUI hopes this is a vivid and watertight illustration of the concepts involved. The good doctor has also provided a sample prescription in the form of the code sample download for this article that will list all of the symbol fonts available on your computer.
So far, so good, as all this sounds simple enough. In fact, what Dr. GUI has prescribed so far should hold you in pretty good stead, as long as you try to retrieve GdiCharSet for a Font object whose GdiCharSet property has been set. However, you're bound to run into time-consuming roadblocks when you try to retrieve GdiCharSet for a Font object whose GdiCharSet property has not been set. In the previous example, Visual Studio .NET takes care of setting GdiCharSet for the Font object associated with your FontCheck button. Thus, you wouldn't notice anything unexpected. However, let's see what happens when you use the following code in the FontCheck_Click event-handler.
C# code:
Font MyFont = new Font("Wingdings",10);
MessageBox.Show(MyFont.GdiCharSet.ToString());
Visual Basic .NET code:
Dim MyFont As New Font("Wingdings", 10)
MessageBox.Show(MyFont.GdiCharSet)
When you run your application and then click the FontCheck button on Form1, you'd naturally expect a message box with 2 as the text, because Wingdings is, after all, a symbol font. However, you end up with a message box that has 1 as the text. Confused? Understandable but not incurable! Let Dr. GUI provide his diagnosis of this case...
When you create a Font object, GdiCharSet is set to the value of DEFAULT_CHARSET, unless, in the overloaded constructor, you explicitly specify the character set you wish to use. Checking the table discussed previously, DEFAULT_CHARSET maps to the value 1, which is exactly what the output text in your message box indicates. If, on the other hand, in the constructor of your Font object, you explicitly pass a parameter to specify the character set you'd like your Font object to use, GdiCharSet gets set with this value that you pass. So, if you want GdiCharSet to contain a meaningful value, the onus is on you to specify the appropriate parameter in the constructor when you create your Font object. Once you've created your Font object by specifying the character set to be used, you can obtain the character set used by retrieving the value of GdiCharSet.
However, supplying the character set in the constructor defeats the very purpose of what you're trying to achieve. Dr. GUI fully appreciates the redundancy involved here. Just the same, let's assume that you have a method that receives a Font object created by some code not under your control. In such a scenario, you have no way of knowing whether or not the GdiCharSet property for the Font object that your method receives contains a meaningful value. Without a clear understanding of Dr. GUI's diagnosis, you might be confused as to why a Font object, which has Wingdings as the Name property, returns 1 for the value of GdiCharSet, when you would clearly expect to get 2. Again, this is because the GdiCharSet property was not set when the Font was created.
With this apparent inconsistency clarified, it should be clear that GdiCharSet can solve only some of your ills. However, if the Font object in question is created without passing the character set value to the constructor, there's still the possibility of your being stumped. So, in order to empower you with some co-relation between your Font object and the character set used by the Font object, the Font class has a neat little method called ToLogFont.
In fact, the code that you've supplied to Dr. GUI is already on the right track. Use ToLogFont to populate a LOGFONT instance that is filled based on only some of the properties of your Font object. To brush up on LOGFONT, check out LOGFONT in the Online MSDN Library. Significantly, the lfCharSet field of your LOGFONT instance does not get set to DEFAULT_CHARSET. Rather, lfCharSet gets set to the value corresponding to the character set used by your Font object. Thus, even if the GdiCharSet property of your Font object contains DEFAULT_CHARSET, once you call ToLogFont, the lfCharSet field of your LOGFONT instance gets sets to 2—if the Name property of your Font object is Wingdings. You can now obtain what you desire by retrieving the value of the lfCharSet field of your populated LOGFONT instance.
But, before all that, you'll need to reference the System.Runtime.InteropServices namespace so that you'll be able to appropriately declare LOGFONT. So, go ahead and add the following statement to your code:
C# code:
using System.Runtime.InteropServices;
Visual Basic .NET code:
Imports System.Runtime.InteropServices
Dr. GUI couldn't help but notice that in your quest for a solution to your malady, you've already expertly converted the unmanaged LOGFONT structure to the best possible managed equivalent. Way to go! However, as Dr. GUI will explain presently, the good doctor advocates the use of a LOGFONT class, rather than a LOGFONT structure. This is where you've stumbled in your attempt and come up against a brick wall. So, let's go ahead and use your LOGFONT declaration, using "Class" and "class", instead of "Structure" and "struct", respectively.
C# code:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
class LOGFONT
{
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
public string lfFaceName;
}
Visual Basic .NET code:
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Public Class LOGFONT
Public lfHeight As Integer
Public lfWidth As Integer
Public lfEscapement As Integer
Public lfOrientation As Integer
Public lfWeight As Integer
Public lfItalic As Byte
Public lfUnderline As Byte
Public lfStrikeOut As Byte
Public lfCharSet As Byte
Public lfOutPrecision As Byte
Public lfClipPrecision As Byte
Public lfQuality As Byte
Public lfPitchAndFamily As Byte
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
Public lfFaceName As String
End Class
Of course, since you're using a class rather than a structure, you'll need to explicitly instantiate your LOGFONT instance. The rest of the code advocated by Dr. GUI is in harmony with your code. So, without much further ado, let's have a look at Dr. GUI's code:
C# code:
Font MyFont = new Font("Wingdings",10);
LOGFONT MyLOGFONT = new LOGFONT();
MyFont.ToLogFont(MyLOGFONT);
// MyLOGFONT is populated.
MessageBox.Show(MyLOGFONT.lfCharSet.ToString());
Visual Basic .NET code:
Dim MyFont As New Font("Wingdings", 10)
Dim MyLOGFONT As New LOGFONT
MyFont.ToLogFont(MyLOGFONT)
' MyLOGFONT is populated.
MessageBox.Show(MyLOGFONT.lfCharSet)
Now, all that remains is to understand why you need to use a class instead of a structure.
As you've probably already noticed, ToLogFont expects you to pass an object by value. Thus, you could pass either a structure by value or a class by value and you'd still be perfectly within your rights as far as the rules relating to passing valid types. However, what you're doing is passing a System.ValueType by value, when you should be passing an object reference. Let's look at why.
The behavior you noted in your sample above (where the LOGFONT is not being filled in) is because ToLogFont receives a copy of your LOGFONT structure, which gets populated. However, these changes are not reflected in your original LOGFONT structure, which remains unpopulated. On the other hand, if you were to pass a reference to your LOGFONT instance, you would ensure that your original reference and the copy of the reference are, in fact, referring to the same LOGFONT object instance. Thus, when you declare LOGFONT as a class, the changes made to the single LOGFONT instance by ToLogFont, using a copy of your original reference, are reflected in your code.
For more details about parameter passing, check out Passing Parameters in the MSDN Library. As this piece is geared towards C# (though the concepts hold good for Visual Basic .NET too), here's something for Visual Basic .NET folks: Argument Passing Mechanism.
When you run your application, and then click the FontCheck button that is on Form1, you are rewarded for your perseverance with the result you've been waiting for all along: a message box with 2 as the text. You can try this code out for other fonts as well, and convince yourself of the veracity of this prescription. The good doctor hopes this solves the problem for you.
Thanks!
Dr. GUI wants to thank his fabulous team of specialists, including Ajay Abraham, and his dedicated nurse Maura Baughman. If Dr. GUI did surgery without y'all, the prognosis of the patients would not be good.
Ask Dr. GUI