The Graphics object’s MeasureString method tells you approximately how big a string will be when drawn on that object. Its MeasureCharacterRanges method enables you to get more information about the positioning of ranges within a string.
The FontFamily class provides some additional methods that a program can use to get more information about how characters are drawn. Before you can use these values, you must understand a bit of extra character anatomy.
Figure 22-12 shows how a font’s internal leading, ascent, descent, and external leading values help determine a character’s position.
Figure 22-12: How text is positioned depends on many font metrics, including internal leading, ascent, descent, and external leading.
The following table describes these font metrics.
Value | Meaning |
---|---|
Internal Leading | Extra space left above the characters but considered part of the string |
Em Height | The height within which the characters are drawn |
Ascent | The part of the character cell above the baseline |
Descent | The part of the character cell below the baseline |
Cell Height | The height of the character area including internal leading |
External Leading | Extra space left below one line and above the next |
Line Spacing | The distance between one line and the next |
From the figure, you can verify the following relationships:
Cell Height = Ascent + Descent = Internal Leading + Em Height Line Spacing = Cell Height + External Leading
The FontFamily object provides several methods for determining font metric values. These methods include GetCellAscent, GetCellDescent, GetEmHeight, and GetLineSpacing.
All of these methods return values in font design units. The key to converting them into some other unit is to realize that the Font object’s Size property returns the font’s em size in whatever units the font is currently using. For example, if you specify the font’s size in pixels, then Font.Size returns the em size in pixels.
Using Font.Size and the value returned by the FontFamily class’s GetEmHeight method, you can convert the other values into pixels. For example, the following equation shows how to calculate a font family’s ascent in pixels:
Ascent Pixels = FontFamily.GetCellAscent * Font.Size / FontFamily.GetEmHeight
The following code draws the font metrics for some text in three different fonts. It starts by defining some text, a layout rectangle, and a StringFormat object. Then, for each of three fonts, the code creates the font and calls subroutine MeasureCharacters to display the font metrics. Subroutine MeasureCharacters defines an array of CharacterRange objects and initializes them so that they each refer to a single character in the string. It calls SetMeasurableCharacterRanges and then MeasureCharacterRanges, as described in the section “MeasureString” earlier in this chapter. Next, the code calculates the font’s em height, ascent, descent, cell height, internal leading, line spacing, and external leading. The program then loops through the Regions returned by MeasureCharacterRanges. It converts each Region into a Rectangle and draws it. It then draws lines showing the internal leading, ascent, and descent values and fills an area representing the external leading space. The subroutine finishes by drawing the text.
Private Sub Form1_Paint(ByVal sender As Object, _ ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint e.Graphics.TextRenderingHint = _ System.Drawing.Text.TextRenderingHint.AntiAliasGridFit Dim txt As String = "Mgfi" Dim layout_rect As New RectangleF(0, 0, _ Me.ClientSize.Width \ 3, Me.ClientSize.Height) Using string_format As New StringFormat string_format.LineAlignment = StringAlignment.Center string_format.Alignment = StringAlignment.Center Using the_font As New Font("Times New Roman", 80, _ FontStyle.Bold, GraphicsUnit.Pixel) MeasureCharacters(e.Graphics, the_font, txt, _ layout_rect, string_format) End Using layout_rect.X += Me.ClientSize.Width \ 3 Using the_font As New Font("Comic Sans MS", 80, _ FontStyle.Bold, GraphicsUnit.Pixel) MeasureCharacters(e.Graphics, the_font, txt, _ layout_rect, string_format) End Using layout_rect.X += Me.ClientSize.Width \ 3 Using the_font As New Font("Courier New", 80, _ FontStyle.Bold, GraphicsUnit.Pixel) MeasureCharacters(e.Graphics, the_font, txt, _ layout_rect, string_format) End Using End Using ' string_format End Sub Public Sub MeasureCharacters(ByVal gr As Graphics, ByVal the_font As Font, _ ByVal txt As String, ByVal layout_rect As RectangleF, _ ByVal string_format As StringFormat) ' Define an array of CharacterRange objects, ' one for each character. Dim character_ranges(txt.Length - 1) As CharacterRange For i As Integer = 0 To txt.Length - 1 character_ranges(i) = New CharacterRange(i, 1) Next i ' Set the ranges in the StringFormat object. string_format.SetMeasurableCharacterRanges(character_ranges) ' Get the character range regions. Dim character_regions() As Region = _ gr.MeasureCharacterRanges(txt, _ the_font, layout_rect, string_format) ' Get the font's ascent. Dim em_height As Integer = the_font.FontFamily.GetEmHeight(FontStyle.Bold) Dim em_height_pix As Single = the_font.Size Dim design_to_pixels As Single = the_font.Size / em_height Dim ascent As Integer = the_font.FontFamily.GetCellAscent(FontStyle.Bold) Dim ascent_pix As Single = ascent * design_to_pixels Dim descent As Integer = the_font.FontFamily.GetCellDescent(FontStyle.Bold) Dim descent_pix As Single = descent * design_to_pixels Dim cell_height_pix As Single = ascent_pix + descent_pix Dim internal_leading_pix As Single = cell_height_pix - em_height_pix Dim line_spacing As Integer = _ the_font.FontFamily.GetLineSpacing(FontStyle.Bold) Dim line_spacing_pix As Single = line_spacing * design_to_pixels Dim external_leading_pix As Single = line_spacing_pix - cell_height_pix ' Draw each region's bounds. For Each rgn As Region In character_regions ' Convert the region into a Rectangle. Dim character_bounds As RectangleF = rgn.GetBounds(gr) Dim character_rect As Rectangle = _ Rectangle.Round(character_bounds) ' Draw the bounds. gr.DrawRectangle(Pens.Black, character_rect) ' Draw the internal leading. gr.DrawLine(Pens.White, _ character_rect.X, _ character_rect.Y + internal_leading_pix, _ character_rect.Right, _ character_rect.Y + internal_leading_pix) ' Draw the ascent. gr.DrawLine(Pens.Yellow, _ character_rect.X, _ character_rect.Y + ascent_pix, _ character_rect.Right, _ character_rect.Y + ascent_pix) ' Draw the descent. gr.DrawLine(Pens.Orange, _ character_rect.X, _ character_rect.Y + ascent_pix + descent_pix, _ character_rect.Right, _ character_rect.Y + ascent_pix + descent_pix) ' Draw the external leading. gr.FillRectangle(Brushes.Red, _ character_rect.X, _ character_rect.Y + ascent_pix + descent_pix, _ character_rect.Width, _ external_leading_pix) Next rgn ' Draw the text. gr.DrawString(txt, the_font, Brushes.Black, _ layout_rect, string_format) End Sub
Figure 22-13 shows the result.
Note that the font metrics are not always rigidly followed. For example, sometimes a character may exted into the external leading space.
Figure 22-13: The FontFamily and Font classes provide the methods you need to calculate font metrics.