MULTIMEDIA

Programming .NET Framework 3.5 : Placing Text

6/5/2012 9:12:49 AM
In a graphical world, text is treated as a graphical object. This has some nice benefits. You can freely mix text and graphical images together. You can also use several fonts together to create a richer style of output. The drawback to this added power is that some effort is required to coordinate the placement of the various graphical objects in the available drawing area. By contrast, a console-style application can send its text to an output window, where automatic word wrapping and automatic scrolling take the worry out of creating text output. In a graphical environment, you—the programmer—must measure the space that text occupies. The reward for this effort is total control in text placement.

Even if you are drawing only text, with no other kind of graphical object, you still have some work to do. For example, drawing multiple lines of text requires you to calculate the height of each line of text so that you can position each line the correct distance from the previous line. For this and other text-measuring tasks, the .NET Compact Framework provides the MeasureString method, which resides in the Graphics class. We discuss this next.

1. Text Size and the MeasureString Method

Most people have looked at text their entire lives. Yet, using text to read and write is not the same as managing the display of graphical text. If you look closely at individual characters—for example, the letter G—you see a very complicated piece of artwork that a font designer spent many hours perfecting. To efficiently deal with text without getting lost in each glyph, graphics programmers simplify a text string.[4]

[4] A glyph is the graphical image of a character. Display and printer driver writers, among others, use this term to make sure that the output—the mapping from the character in a font on a specific device—looks correct. Two images of the letter A—one drawn with 10-point Arial and the other with 12-point Arial—represent two glyphs but the same character code.

We handle graphical text in terms of an imaginary bounding box that surrounds the text string. To place graphical text, we organize the placement of the bounding box within the drawing area, relative to other text and nontext graphical objects. This simplification is the basis of all efforts to measure the size of text in a graphics system.

The MeasureString method returns the width and height of a text string by providing a SizeF structure. The F at the end of the structure name indicates that the structure contains floating-point values (a Hungarian-like suffix). For our purposes, SizeF has two interesting properties:

public float Width {get; set;}
public float Height {get; set;}

The MeasureString method returns the height and width for a specified string drawn with a given font. To calculate the bounding box, we specify both the character string and the font to be used in drawing the string. Here is the definition of the MeasureString method:

public SizeF MeasureString(
 string text,
 Font font);

2. A Sample Program: MeasureString

The MeasureString method is simple, so the accompanying sample program is also simple. The greatest challenge is that the bounding box size is always provided as floating-point values. You must create conversion logic if your text-drawing code interacts with other code that uses integer coordinates. Among other things, this includes mouse input coordinates and the coordinates for drawing vector or raster graphics.

A text box allows the user to type in some text. As shown in Figure 1, the sample draws white text on a black background to highlight the size and placement of the bounding box. We also went a little crazy with some line drawing to illustrate the relationship between the reported height and width and the resulting text. Listing 1 provides a fragment of the code in the Paint event handler for measuring and drawing the string.

Figure 1. MeasureString Illustrating Exactly What Is Being Measured


Listing 1. Fragment Showing the Call to the MeasureString Method
string str = this.Text;
Brush brFore = new SolidBrush(SystemColors.Window);
Brush brBack = new SolidBrush(SystemColors.WindowText);

// Upper-left corner of text box.
float sinX = (float)(labelWidth.Right/5);
float sinY = (float)(labelHeight.Bottom + 12);

SizeF szf = e.Graphics.MeasureString(str, Font);

// Draw rectangle in text background.
int xRect = (int)sinX;
int yRect = (int)sinY;
int cxRect = (int)szf.Width;
int cyRect = (int)szf.Height;
Rectangle rc;
rc = new Rectangle(xRect, yRect, cxRect, cyRect);

e.Graphics.FillRectangle(brBack, rc);

// Draw string.
e.Graphics.DrawString(str, Font, brFore, sinX, sinY);

The bounding box for the text is visible in Figure 12.6. When we specify the location for the text, the drawing coordinate in the DrawString method corresponds with the upper-left corner of the bounding box. Our next sample program, TextAlign, shows how to get eight other alignments.

You might notice that the top of the bounding box contains a blank horizontal area. In the world of digital typography, this is referred to as the internal leading. The term leading comes from typesetting as done in the predigital era when cubes holding character images had to be individually placed to form lines of text. Using this printing technology, a typesetter placed a long, thin piece of metal—known as the leading—between the rows of letters to provide the interline spacing. In the world of digital fonts, leading is split into two parts: the internal leading and the external leading. These two names indicate whether or not the leading is included in the reported height.

The interline spacing that the MeasureString method reports works reasonably well in most cases, especially given the small screen size of a typical device such as a Pocket PC that runs .NET Compact Framework programs. Programmers who work in depth with digital font technology may notice that absolute precision in text placement requires an additional element, the external leading value. For many TrueType fonts at smaller sizes, the external leading is zero and so can often be ignored.

3. Text Alignment

The .NET Compact Framework supports four overloads to the DrawString method. One overload positions the text with an (x,y) coordinate pair. We use this overload in all the samples presented up to this point. The second overload positions the text within a rectangle, and it performs word wrapping of the text and clipping to the rectangle. 

When text is drawn with the coordinate-pair version of the DrawString method, the text is drawn below and to the right of the point. In other words, the default text alignment is to the top and left of the text box. For example, in Figure 2, the text coordinate is the intersection of the vertical and horizontal lines.

Figure 2. Default Text Alignment


This approach has been part of every version of Windows since Microsoft Windows 1.01 shipped in 1985. It is likely an outgrowth of the fact that the default origin of a drawing surface is the upper-left corner, which no doubt results from the convention in European languages of reading and writing from left to right and from top to bottom.[5]

[5] Not every language follows this convention. Some languages are read from right to left.

Whatever the origin, the default alignment is not always the most convenient. This alignment does not help when you try to center some text within an object (e.g., a rectangle or a window). Nor does this default alignment help center text over a column of numbers.

The .NET Compact Framework does not provide automatic text alignment. For other alignments, you calculate an offset from the desired point to the upper-left corner of the text box.

4. A Sample Program: TextAlign

The TextAlign sample program lets you visualize the nine possible text alignments. There are nine possible alignments because there are three vertical alignment values (near, center, and far) and three horizontal alignment values (near, center, and far). The default for text alignment, shown in Figure 3, is near and near, which corresponds to the text drawn with the upper-left corner of the text box at the reference point indicated in the DrawString call.

Figure 3. One of Nine Possible Text Alignments


There is an alternative to fussing with these alignment values: You can call one of the versions of the DrawString function which accepts the bounding rectangle as input. It is nice to have choices, which is a key benefit of having overloaded functions in an object-oriented programming language.

Listing 2 shows the code for the TextAlign sample program.

Listing 2. TextAlign Program Showing How to Support Nine Text Alignment Settings
public partial class formMain : Form
    {
    // Text alignment flags.
    StringAlignment valign;
    StringAlignment halign;

    // Adjustment for bounding box.
    private int m_cxAdjust = 0;
    private int m_cyAdjust = 0;

    public formMain()
    {
        InitializeComponent();

        // Set initial alignment values.
        valign = StringAlignment.Near;
        halign = StringAlignment.Near;
    }

    private void mitemExit_Click(object sender, EventArgs e)
    {
        Close();
    }

    private void
    mitemAlignHorizontal(object sender, EventArgs e)
    {
        var mi = (MenuItem)sender;

        // Set label to menu name.
        labHorz.Text = mi.Text;

        // Calculate size of string bounding box.
        Graphics g = CreateGraphics();
        SizeF size = g.MeasureString(this.Text, this.Font);
        g.Dispose();

        // Set flag and
        // Calculate bounding box offset.
        if (mi.Text == "Near")
        {
            halign = StringAlignment.Near;
            m_cyAdjust = 0;
        }
        else if (mi.Text == "Center")
        {
            halign = StringAlignment.Center;
            m_cyAdjust = (int)(size.Height / 2);
        }
        else if (mi.Text == "Far")
        {
            halign = StringAlignment.Far;
            m_cyAdjust = (int)size.Height;
        }

        // Force a redraw.
        Invalidate();
    }

    private void
    mitemAlignVertical(object sender, EventArgs e)
    {
        var mi = (MenuItem)sender;

        // Set label to menu name.
        labVert.Text = mi.Text;

        // Calculate size of string bounding box.
        Graphics g = CreateGraphics();
        SizeF size = g.MeasureString(this.Text, this.Font);
        g.Dispose();

        // Set flag and
        // Calculate bounding box offset.
        if (mi.Text == "Near")
        {
            valign = StringAlignment.Near;
            m_cxAdjust = 0;
        }
        else if (mi.Text == "Center")
        {
            valign = StringAlignment.Center;
            m_cxAdjust = (int)(size.Width / 2);
        }
        else if (mi.Text == "Far")
        {
            valign = StringAlignment.Far;
            m_cxAdjust = (int)size.Width;
        }

        // Force a redraw.
        Invalidate();
    }

    private void
    formMain_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;

        // Calculate reference point for text drawing.
        float xDraw = (float)(ClientRectangle.Right / 2);
        float yDraw = (float)(ClientRectangle.Bottom / 2);

        // Create drawing objects.
        Brush brBlack = new SolidBrush(Color.Black);
        Pen penBlack = new Pen(Color.Black);

        // Create StringFormat for text drawing details.
        var sf = new StringFormat();
        sf.FormatFlags = StringFormatFlags.NoClip;
        sf.Alignment   = valign;
        sf.LineAlignment = halign;

        // Calculate size of string bounding box.
        SizeF size = g.MeasureString(this.Text, Font);
        int cxWidth = (int)size.Width;
        int cyHeight = (int)size.Height;

        // Adjust values to accommodate alignment request.
        float sinTextX = (float)(xDraw - m_cxAdjust);
        float sinTextY = (float)(yDraw - m_cyAdjust);

        // Draw text bounding box.
        Brush hbrFill = new SolidBrush(Color.Gray);
        Rectangle rc = new Rectangle((int)sinTextX,
                                     (int)sinTextY,
                                     cxWidth,
                                     cyHeight);
        g.FillRectangle(hbrFill, rc);

        // Draw text string.
        g.DrawString(this.Text, Font, brBlack, xDraw,
                     yDraw, sf);

        // Draw reference cross-hairs.
        g.DrawLine(penBlack, (int)xDraw, 0, (int)xDraw,
                   this.Height);
        g.DrawLine(penBlack, 0, (int)yDraw, this.Width,
                  (int)yDraw); }					  
Other  
  •  Macro Marvel by Peiling Lee
  •  Flora - Nature - Photo Expert (Part 6) - Fungi
  •  Flora - Nature - Photo Expert (Part 5) - Creative blur
  •  Flora - Nature - Photo Expert (Part 4) - Viewpoint
  •  Flora - Nature - Photo Expert (Part 3) - Flowers, Depth-of-field & Lighting
  •  Flora - Nature - Photo Expert (Part 2) - Len accessories, Lighting aids, Tripods and alternative camera supports
  •  Flora - Nature - Photo Expert (Part 1)
  •  Epic Moments in Sports (Part 2)
  •  Epic Moments in Sports (Part 1)
  •  Hanns.G HL272 27” LED display
  •  PhotoDirector 3 - Gets snap happy
  •  Microsoft Sued Comet For Making 94,000 Copies Of Counterfeit Windows
  •  TV Became Smarter & Friendlier : Ubuntu TV, MySpace TV released
  •  Provide Resources For The Olympics (Part 2)
  •  Provide Resources For The Olympics (Part 1)
  •  Fujifilm Released The First Version Of CSC
  •  OLED Technology Casts A Spell On Big Screen TV
  •  Netflix Introduces Streaming Services In England
  •  Intel Introduced The Future Of Ultrabook
  •  Dolby Digital Plus On Netflix
  •  
    Top 10
    Primer – Choosing And Using Peripheral Buses (Part 2)
    Primer – Choosing And Using Peripheral Buses (Part 1)
    SanDisk ReadyCache 32GB - Caching Solution SSD
    Windows 8 Tips And Tricks – Jan 2013
    Lenovo IdeaPad S400 - Stylish And Affordable Laptop
    Nokia Lumia 920 - Super Smart, Super-Size Handset
    Optimus L9 - The Nicest Phone In LG's 'L' Line
    Bits Of Bytes
    Happy iMas (Part 2)
    Happy iMas (Part 1)
    Most View
    Windows Server 2003 : Planning for Disaster
    BizTalk 2006 : Dealing with Extremely Large Messages (part 2) - Large Message Encoding
    Propellerhead Balance Recording Interface
    Improvements in Mobile Computing in Windows Server 2008 R2
    Share Photos From Your Android Phone
    CPU System Workshop (Part 2) - Corsair Graphite Series 600t, Intel Core I7-3960x Extreme Edition
    Dell Inspiron 15R - The Perfect Budget Laptop
    SharePoint 2010 : SQL Server Database Mirroring for SharePoint Farms
    Acer Aspire 5560G
    SQL Azure Data Access
    eSoft InstaGate 404s - Pricey And Easily Overtaxed
    Super-Thin LED Screen From Viewsonic : VX246h-LED monitor
    Windows Server 2003 : Active Directory Federation Services - The Flow of Applications and Claims, Collaboration with Windows SharePoint Services
    IIS 7.0 : Managing Configuration - Delegating Configuration (part 1)
    Miscs Apps for iOS Device (April 2012) : SleepKeeper, Cristiano Ronaldo, NatureTap
    Intel vs AMD - The Choice Is Harder Than Ever (Part 1)
    Choosing The Right Parts For Your Build (Part 6) - Picking the right RAM, Picking the right cooling, SLI and CrossFire
    File and Disk Recover And Restore (Part 2) - PC Tools File Recover, Piriform Recuva, Ubuntu Rescue Remix
    Exchanging XML Messages over HTTP
    Iweb And Its Replacement (Part 3)