Fonts


A font is an instance of the Font class, which includes a font family, a size, and a font style. And, as you might expect, a font family is an instance of the FontFamily class, which is 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, and underline. 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, including pixels, points, 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 1/72nd of an inch no matter what device it's drawn on, and the Graphics object will scale appropriately as text is drawn. Converting between points and pixels requires knowing the dpi of the device you're drawing on, which is conveniently available via the Graphics.DpiY property: [1]

[1] There's also a Graphics.DpiX property, but that's used for measuring width. Points specify the height of a font.

 using( Graphics g = this.CreateGraphics() ) {  // A 12-point font will be 16 pixels high on a 96-dpi monitor   float dpi = g.DpiY;   float points = 12f;   float pixels = (points * dpi)/72f;  ... } 

The em unit of measure is so named because metal typesetters used capital M as the guide against which all other letters were measured. M was used because it took up the most vertical and horizontal space. The number of points that a font is specified represents "one em" for that font.

Finally, design units are a font designer's way to specify a font family's dimensions no matter what 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 itself is shown here:

 sealed class Font :   MarshalByRefObject, ICloneable, ISerializable, 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 Italic { get; }   public string Name { get; }   public float Size { get; }   public float SizeInPoints { get; }   public bool Strikeout { get; }   public FontStyle Style { get; }   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(object lf);   public static Font FromLogFont(object lf, IntPtr hdc);   public float GetHeight();   public float GetHeight(float dpi);   public float GetHeight(System.Drawing.Graphics graphics);   public IntPtr ToHfont();   public void ToLogFont(object logFont);   public void ToLogFont(object logFont, Graphics graphics); } 

Creating Fonts

You 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'll get an instance of the MS Sans Serif font in whatever size you specify. If you'd like to specify the font in a unit other than points, you can do so using an overload of the Font constructor that takes a value from the GraphicsUnit enumeration:

 enum GraphicsUnit {   Display, // 1/75th of an inch (1/100th of an inch for printers)   Document, // 1/300th of an inch   Inch, // 1 inch   Millimeter, // 1 millimeter   Pixel, // 1 device-dependent pixel   Point, // 1/72nd of an inch   World, // discussed in Chapter 6: Advanced Drawing } 

Except for GraphicsUnit.Pixel and GraphicsUnit.World, all the units are really just variations of a point, because they're all specified in device-independent units. Using these units, all the following specify 12-point Arial: [2]

[2] A dpi of 96 is assumed, which yields 16 pixels for a 12-point font.

 // Can't use GraphicsUnit.Display for creating fonts, // 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); 

If you'd like to specify a style other than regular, you can do so by passing a combination of the values from the FontStyle enumeration:

 enum FontStyle {   Bold,   Italic,   Regular, // default   Strikeout,   Underline, } 

For example, the following will create 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 will be thrown.

If you've got 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  ); 

Font Families

When 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:

 sealed class FontFamily : MarshalByRefObject, 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. A FontFamily can be constructed with one of the GenericFontFamilies enumeration values:

 enum GenericFontFamilies {   Monospace, // Courier New   SansSerif, // Microsoft Sans Serif   Serif, // Times New Roman } 

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); 

If, instead of hard-coding a font family (even a generic one), you'd like to let users pick their favorite, you need to present them 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 of which are features that support interoperability with Win32.

Font Characteristics

After you've got a Font object, you can interrogate it for all kinds of properties, such as its family, its name (which will be the same as the family name), and a couple of GDI properties for Win32 interoperability. Most importantly, you'll 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, the size returned from MeasureString assumes that the string is clipped to a single line; this means that the width will vary with the width of the string, but the height will be 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:

[3] Although individual character heights vary, the vertical space reserved for them does not.

 SizeF layoutArea = this.ClientRectangle.Size; // New line character '\n' will force 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('\n') ) {   float width = manualPanel.ClientRectangle.Width;   float height = manualPanel.ClientRectangle.Height - y;   RectangleF layoutRect = new RectangleF(0, y, width, height);   // Turn off auto-wrapping (we're doing it manually)   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 for embedded new line characters, just as DrawString does when it does the wrapping for us. We also set up a StringFormat (more about that later) that turns off wrapping; otherwise , DrawString will wrap 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 5.1 shows what DrawString would do with a multiline string automatically, and what our manual code does.

Figure 5.1. Automatic Word-Wrap Performed by DrawString Compared with Manual Word-Wrap Using Font.GetHeight

In addition to the strings, Figure 5.1 shows the rectangles obtained by measuring each string: one rectangle when DrawString wraps the text for us, 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 will produce a size that's larger than it needs to be to meet that guarantee.

Font Height

While 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 [4] but also the Size property, which is 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). 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 typesetting used to be done with lead). 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 5.2. Together, these three values make up the line spacing, which is also provided as a property on the FontFamily and is used to calculate the font's height and leading (leading isn't available directly).

[4] The Font also provides a Height property, but it should be avoided in favor of the GetHeight method. The GetHeight method scales to a specified Graphics object, whereas the Height property scales only to the current video adapter's dpi, making it pretty worthless for anything except the nontransformed video adapter.

Figure 5.2. 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 5.1 shows the various measures of font and font family height.

Table 5.1. Font and FontFamily Sizes (Sample Font Is Arial 12 Point at 96 dpi)

Measure

Units

Example

Description

FontFamily.GetEmHeight

Design Units

2,048

Base size, equivalent to Size

FontFamily.GetCellAscent

Design Units

1,854

Height above base line

FontFamily.GetCellDescent

Design Units

434

Height below base line

FontFamily.GetLineSpacing

Design Units

2,355

CellAscent + CellDescent + Leading, normally about 115% of EmHeight

Leading

Design Units

67

Extra space below bottom of CellDescent for readability, not exposed by any property

Font.Size

GraphicsUnit passed to Font ctor (defaults to Point)

16 pixels

Base size, equivalent to EmHeight

Font.SizeInPoints

Points

12 points

Base size in points, equivalent to Size and EmHeight

Font.GetHeight

Pixels

18.39844

Equivalent to LineSpacing scaled to either Graphics object or dpi

Font.Height

Pixels

19

Equivalent to LineSpacing scaled to system dpi and rounded to next-highest integer value

Scaling Factor

Design Unit or Pixels

128

Used to convert design units to physical or logical values for rendering



Windows Forms Programming in C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net