A font is an instance of the System.Drawing.Font class, which for each font includes a family, a style, and a size. And, as you might expect, a font family is an instance of the FontFamily class, which encapsulates a group of typefaces that differ only in style. A typeface is a named collection of drawing strokes that make up the outlines of the characters, such as those you're reading right now. It's the typeface name that you're used to seeing in the "Font" menu of most programs. The font style constitutes the variations within a typeface, such as bold, italics, underline, and size. So, a typeface would be Arial, a font family would include Arial Regular and Arial Bold, and a font would be 12-point Arial Bold. Fonts can be measured in several sizes other than points, including pixels, ems, and design units. A pixel is a point of light on a screen or a point of ink on a printer. Pixels are often packed into inches for measurement. For example, the resolution of video display adapters and printers is typically specified in dots per inch (dpi), where a dot is the same as a pixel. Pixels are device-dependent, so a pixel on a 72-dpi display bears no size relationship to a pixel on a 300-dpi printer. A point, on the other hand, is inch no matter what device it's drawn on, and the Graphics object scales appropriately as text is drawn. If you want to ensure that a font is rendered to the target device in the correct size, you need to convert between points and pixels. This requires knowing the dpi of the device you're drawing on, which is conveniently available via the Graphics.DpiY property:[1]
using( Graphics g = this.CreateGraphics() ) { // A 12-point font is 16 pixels high on a 96-dpi monitor float dpi = g.DpiY; float points = 12f; float pixels = (points * dpi)/72f; //=16f ... } The em unit of measure is so named because metal typesetters used uppercase M as the guide against which all other letters were measured. M was used because it took up the most vertical and horizontal space. Consequently, the number of points specified for a font represents "one em" for that font. Finally, design units are a font designer's way to specify a font family's dimensions regardless of the resolution of the rendering device or the size of the rendered font. For example, Arial has a height of 2,048 design units. The design units are used to scale a font family to a point size when individual strokes of the font are rendered (more on this later). The Font class is shown here:
namespace System.Drawing { sealed class Font : IDisposable, ... { // Constructors public Font(...); // Several overloads // Properties public bool Bold { get; } public FontFamily FontFamily { get; } public byte GdiCharSet { get; } public bool GdiVerticalFont { get; } public int Height { get; } public bool IsSystemFont { get; } // New public bool Italic { get; } public string Name { get; } public float Size { get; } public float SizeInPoints { get; } public bool Strikeout { get; } public FontStyle Style { get; } public string SystemFontName { get; } // New public bool Underline { get; } public GraphicsUnit Unit { get; } // Methods public static Font FromHdc(IntPtr hdc); public static Font FromHfont(IntPtr hfont); public static Font FromLogFont(...); public float GetHeight(...); public IntPtr ToHfont(); public void ToLogFont(...); } } Creating FontsYou can create a Font object by specifying, at a minimum, the typeface and the size in points: using( Font font = new Font("Arial", 12) ) {...} If you specify a font that's not available, you get an instance of the MS Sans Serif font in the size you specify. To specify the font in a unit other than points, you use an overload of the Font constructor that takes a value from the GraphicsUnit enumeration: namespace System.Drawing { enum GraphicsUnit { World = 0, // discussed in Chapter 7: Advanced Drawing Display = 1, // 1/75 inch (1/100 inch for printers) Pixel = 2, // 1 device-dependent pixel Point = 3, // 1/72 inch Inch = 4, // 1 inch Document = 5, // 1/300 inch Millimeter = 6, // 1 millimeter } } Except for GraphicsUnit.Pixel and GraphicsUnit.World, all the units are variations of a point, because they're all specified in device-independent units. Using these units, all the following specify 12-point Arial:[2]
// Can't use GraphicsUnit.Display for creating font // because Display varies based on where shapes are drawn Font font1 = new Font("Arial", 12, GraphicsUnit.Point); Font font2 = new Font("Arial", 16, GraphicsUnit.Pixel); Font font3 = new Font("Arial", 0.1666667f, GraphicsUnit.Inch); Font font4 = new Font("Arial", 50, GraphicsUnit.Document); Font font5 = new Font("Arial", 4.233334f, GraphicsUnit.Millimeter); To specify a style other than regular, you pass a combination of the values from the FontStyle enumeration: namespace System.Drawing { enum FontStyle { Regular = 0, // default Bold = 1, Italic = 2, Underline = 4, Strikeout = 8, } } For example, the following creates Arial Bold Italic: Font font = new Font("Arial", 12, FontStyle.Bold | FontStyle.Italic); If the font family you're specifying with the typeface argument to the Font constructor doesn't support the styles you specify, a run-time exception is thrown. If you have a font but you don't like the style, you can create a Font based on another Font. This is handy when you'd like to base a new font on an existing font but need to make a minor adjustment: Font font = new Font(this.Font, FontStyle.Bold | FontStyle.Italic); Note that you can't set font properties like Bold and Italic directly on a Font object because they are read-only. Font FamiliesWhen creating a font, you use the typeface name to retrieve a font family from the list of fonts currently installed on the system. The typeface name is passed to the constructor of the FontFamily class. The FontFamily class is shown here: namespace System.Drawing { sealed class FontFamily : IDisposable, ... { // Constructors public FontFamily(GenericFontFamilies genericFamily); public FontFamily(string name); public FontFamily(string name, FontCollection fontCollection); // Properties public static FontFamily[] Families { get; } public static FontFamily GenericMonospace { get; } public static FontFamily GenericSansSerif { get; } public static FontFamily GenericSerif { get; } public string Name { get; } // Methods public int GetCellAscent(FontStyle style); public int GetCellDescent(FontStyle style); public int GetEmHeight(FontStyle style); public static FontFamily[] GetFamilies(Graphics graphics); public int GetLineSpacing(FontStyle style); public string GetName(int language); public bool IsStyleAvailable(FontStyle style); } } Creating a Font from a FontFamily looks like this: FontFamily family = new FontFamily("Arial"); Font font = new Font(family, 12, FontStyle.Bold | FontStyle.Italic); Creating a Font from a FontFamily object is useful if you'd like to pick a font family based on general characteristics instead of a specific typeface name. You can construct a FontFamily using one of several values provided by the GenericFontFamilies enumeration: namespace System.Drawing.Text { enum GenericFontFamilies { Serif = 0, // Times New Roman SansSerif = 1, // Microsoft Sans Serif Monospace = 2, // Courier New } } Constructing a FontFamily using a value from GenericFontFamilies is useful if you'd like to avoid the risk that a more specific font won't be available on the system. In fact, the FontFamily class even provides properties that you can use directly for each of these FontFamilies: // The hard way Font font1 = new Font(new FontFamily(GenericFontFamilies.Serif), 12); // The easy way Font font2 = new Font(FontFamily.GenericMonospace, 12); To let users pick their favorite font instead of hard-coding a font family (even a generic one), you present a UI that lets them pick from the font families they have installed. The FontFamily class provides the Families property for determining the currently installed font families: foreach( FontFamily family in FontFamily.Families ) { // Can filter based on available styles if( !family.IsStyleAvailable(FontStyle.Bold) ) continue; familiesListBox.Items.Add(family.Name); } You can also construct a Font object from an HDC, an HFONT, or a LOGFONT, all features that support interoperability with Win32. System FontsOne special subset of all the font families installed on your computer is the system fonts, those fonts that are the same for all specific Windows UI items such as the active form's title bar. You can configure system fonts from the Advanced Appearance dialog (Control Panel | Display | Appearance | Advanced), as shown in Figure 6.1 Figure 6.1. Setting Systemwide Fonts
System fonts are automatically used by Windows Forms, but sometimes you may require system font information for your own purposes, such as building a custom control (see Chapter 10: Controls). In these cases, you should use the SystemFonts class, which exposes a subset of eight of the system fonts you can set via Display Properties as static properties: namespace System.Drawing { sealed class SystemFonts { // Properties public static Font CaptionFont { get; } public static Font DefaultFont { get; } public static Font DialogFont { get; } public static Font IconTitleFont { get; } public static Font MenuFont { get; } public static Font MessageBoxFont { get; } public static Font SmallCaptionFont { get; } public static Font StatusFont { get; } // Methods public static Font GetFontByName(string systemFontName); } } Figure 6.2 shows each of the SystemFont properties along with the default Windows XP fonts to which they map. Figure 6.2. Windows XP Default SystemFonts
Unlike other SystemXxx classes, SystemFonts aren't cached by .NET, so you must dispose of any you use: using( Font systemFont = SystemFonts.CaptionFont ) { // Draw some text ... } Font CharacteristicsWhichever way you get a Font object, after you have it, you can interrogate it for all kinds of properties, such as its family, its name (which is the same as the family name), and a couple of GDI properties for Win32 interoperability. Most importantly, you probably want to know about a font's style, using either the Style property of type FontStyle or using individual properties: // The hard way bool bold1 = (this.Font.Style & FontStyle.Bold) == FontStyle.Bold; // The easy way bool bold2 = this.Font.Bold; Another important characteristic of a Font is its dimensions. The width of a character in a specific font varies from character to character, unless you've used a monospace font such as Courier New, in which all characters are padded as necessary so that they're the same width. The Graphics object provides the MeasureString method for measuring the maximum size of a string of characters of a specific font: using( Graphics g = this.CreateGraphics() ) { SizeF size = g.MeasureString("Howdy", this.Font); float length = size.Width; } When it's called this way, MeasureString assumes that the string is clipped to a single line; this means that the width varies with the width of the string, but the height is a constant.[3] Because the Graphics object can wrap multiline strings to a rectangle, you can also measure the rectangle needed for a multiline string. You do this by calling the MeasureString method and specifying a maximum layout rectangle for the string to live in:
SizeF layoutArea = this.ClientRectangle.Size; // New line character '\n' forces text to next line string s = "a string that will\ntake at least two lines"; SizeF size = g.MeasureString(s, this.Font, layoutArea); The Width property returned in the SizeF object is the width of the longest wrapped line, and the Height is the number of lines needed to show the string multiplied by the height of the font (up to the maximum height specified in the layout area). The height used as the multiplier isn't the height of the font as specified. For example, 12 points would be 16 pixels at 96 dpi, but that's not the value that's used. Instead, the height is approximately 115% of that, or about 18.4 pixels for a 12-point font at 96 dpi. This expanded value is exposed from the Font.GetHeight method and is meant to maximize readability when lines of text are drawn one after another. For example, if you wanted to handle wrapping yourself, you could lay out text one line at a time, incrementing the y value by the result of Font.GetHeight: foreach( string line in multiline.Split(Environment.NewLine.ToCharArray()) ) { float width = manualPanel.ClientRectangle.Width; float height = manualPanel.ClientRectangle.Height - y; RectangleF layoutRect = new RectangleF(0, y, width, height); // Turn off autowrapping (we're doing it manually) using( StringFormat format = new StringFormat(StringFormatFlags.NoWrap) ) { g.DrawString(line, this.Font, Brushes.Black, layoutRect, format); ... // Get ready for the next line y += this.Font.GetHeight(g); } } In this code, we split the string into multiple lines using the embedded new line characters, just as DrawString does when it does the wrapping for us. We also set up a String-Format (more about that later) that turns off wrapping; otherwise, DrawString wraps at word boundaries for us. After we draw the string at our chosen rectangle, we increment y by the result of Font.GetHeight so that the next line of text is far enough below the text we just drew to make it pleasing to read. Figure 6.3 shows what DrawString would do with a multiline string automatically, and what our manual code does. Figure 6.3. Automatic Word Wrap Performed by DrawString Compared with Manual Word Wrap Using Font.GetHeight
In addition to the strings, Figure 6.3 shows the rectangles obtained by measuring each string: one rectangle when DrawString wraps the text for us, and one rectangle per line when we do it ourselves. Notice also that the rectangle produced by MeasureString is a bit bigger than it needs to be to draw the text. This is especially evident in the overlapping rectangles shown on the manual side. MeasureString is guaranteed to produce a size that's big enough to hold the string but sometimes produces a size that's larger than it needs to be to meet that guarantee. Font HeightWhile we're on the subject of font height, it turns out that there are a lot of ways to measure the height of a font. The Font class provides not only the GetHeight method but also the Size property, which stores the base size provided in the units passed to the Font object's constructor (the GraphicsUnit value specified at construction time is available via the Font's Unit property).[4] As I mentioned, the height of a font is determined from the base size. The height of the font is further broken down into three parts called cell ascent, cell descent, and leading (so named because typesetters used to use strings of lead to separate lines of text and prevent letters from touching each other). Two of these three measures are available in design units from the FontFamily class (available via the Font's FontFamily property) and are shown in Figure 6.4. Together, these three values make up the line spacing, which is also provided as a property on FontFamily and is used to calculate the font's height and leading (leading isn't available directly).
Figure 6.4. The Parts of a Font Family's Height
The line spacing is expressed in design units but is used at run time to determine the result of calling Font.GetHeight. The magic of the conversion between design units and pixels is managed by one more measure available from the FontFamily class: the em height. The em height is a logical value but is equivalent to the font's size in points, so scaling between design units and pixels is performed using the proportion between the font's size and the font family's em height. For example, the scaling factor between Arial's em height (2,048) and its 12-point pixel height (16 at 96 dpi) is 128. Dividing Arial's line spacing (2,355) by 128 yields 18.39844, which is the same as the result of calling GetHeight on 12-point Arial at 96 dpi. Table 6.1 shows the various measures of font and font family height.
|