Click here to Skip to main content
Click here to Skip to main content

Vertical Text

, 20 Jan 2005
Rate this:
Please Sign up or sign in to vote.
A C# class to draw vertical text strings

Sample Image - Vertical_Text.jpg

Introduction

While working on a Button UserControl project, I had a requirement to write vertical text. I web-searched the usual suspects, but all I found out was how to draw a string rotated in its entirety either 90 or 270 degrees. What I wanted was to draw the string vertically but keep the characters upright. Having found nothing, I decided to do it myself, and wrote the VerticalText class.

Problems encountered

My first attempt was reasonably successful, and not particularly difficult. However, the string was a little "spaced out" as the left-most string in this screenshot shows:

As you can see, the "i" 's may be forgiven for wondering if they are suffering from a personal hygiene problem, and the "s" at the bottom seems to be trying to escape to the next word...

For attempt number two, I've applied a TextSpread value, discussed later, to reduce the height of each character. This is fine for the "s" and "i", but not so good for taller characters, or those with tails. When you have a tall character following a tailed character, such as the "gh", the problem is compounded, and the characters start to overlap.

To solve this problem, I developed a small routine which, depending on the character in question, would return a small offset, to be applied before or after the character was written. The results are the right-hand string, which to my eye at least looks about right.

The Code

The primary method in the VerticalText class is Draw. This method has three overloads, as follows. (The first two overloads ultimately call the last one.)

// Draw the string in the top left corner of the rectangle
public void Draw(Graphics, string, Font, Brush, Rectangle);

// Draw the string in the rectangle, horizontally and vertically aligned
public void Draw(Graphics, string, Font, Brush, Rectangle, StringFormat);

// Draw the string at the specified co-ordinates.
public void Draw(Graphics, string, Font, Brush, int, int);

The second overload calculates the text start co-ordinates, depending on the Alignment and LineAlignment properties of the StringFormat.

public void Draw(Graphics g, string text, Font font, Brush brush,
                 Rectangle stringRect, StringFormat stringStrFmt)
{
    int horOffset;
    int vertOffset
    // Set horizontal offset
    switch (stringStrFmt.Alignment)
    {
        case StringAlignment.Center:
            horOffset = (stringRect.Width / 2) - (int)(font.Size / 2) - 2;
            break;
        case StringAlignment.Far:
            horOffset = (stringRect.Width - (int)font.Size - 2);
            break;
        default:
            horOffset = 0;
            break;
    }

    // Set vertical offset

    double textSize = this.Length(text, font);

    switch (stringStrFmt.LineAlignment)
    {
        case StringAlignment.Center:
            vertOffset = (stringRect.Height / 2) - (int)(textSize / 2);
            break;
        case StringAlignment.Far:
            vertOffset = stringRect.Height - (int)textSize - 2;
            break;
        default:
            vertOffset = 0;
            break;
    }

    // Draw the string using the offsets
    this.Draw(g, text, font, brush,
              stringRect.X + horOffset,
              stringRect.Y + vertOffset);
}

X is determined by using the width of the rectangle, and the width of the font. Y is based on the height of the rectangle, and the vertical length of the text string as calculated by the Length method.

[Description("Length Method - returns vertical length of string")]
public int Length(string text, Font font)
{
    // Put the string into array of chars
    char[] textChars = text.ToCharArray();
    int len = new int();

    for (int i = 0; i < text.Length; i++)
    {
        // Add height of font, times spread factor.
        len += (int)(font.Height * textSpread);
        len += ExtraSpaceAllowance(esaType.Either, 
               textChars[i], font); // Add allowance
    }
    len += 1; // Breathing space...
    return len;
}

The Length method is public, so it may be used by the calling code. This is useful for example when checking that the string you want to draw will actually fit where you want to draw it.

The third overload of the Draw method, called by the first two, does the main job of drawing each individual character, starting at the position passed in x and y, by either the calling program or one of the previous two overloads. This snippet shows the important part:

Rectangle charRect = new Rectangle(x, y, (int)(font.Size * 1.5), font.Height);

// Loop through each character in the string, draw it in the charRect rectangle,
// moving charRect down the screen as appropriate.
for (int i = 0; i < text.Length; i++)
{
    // Move down by character's height allowance BEFORE writing
    charRect.Offset(0, ExtraSpaceAllowance(esaType.Pre, textChars[i], font));

    // Write the character
    g.DrawString(textChars[i].ToString(),font,brush, charRect, charStrFmt);

    // Move down by standard font height
    charRect.Offset(0, (int)(font.Height * textSpread));

    // Move down by character's "dangle" allowance AFTER writing
    charRect.Offset(0, ExtraSpaceAllowance(esaType.Post, textChars[i],font));
}

A small rectangle is positioned at the supplied x and y co-ordinates, with a width and height based on the font. Another StringFormat instance is used, this time to position each character centrally within the rectangle. We then loop through each character in the string, now represented as the textChars Array. Before and after each character is drawn, we call the ExtraSpaceAllowance method.

If you've been paying attention, you'll remember that we bumped into ExtraSpaceAllowance in the Length method, although we weren't formally introduced. This method accepts an esaType, a char and a Font as parameters. The esaType tells us whether to process "tall" characters (esaType.Pre), "tailed" characters (esaType.Post), or either. Once we know what we're doing, we look for the passed character in the appropriate string of qualifying characters. If we find it, we return an int giving the amount of extra pixels to add. This is set to one fifth of the height of the font - this figure was arrived at through trial and error, and it seems to work OK.

private int ExtraSpaceAllowance(esaType type, char ch, Font font)
{

    if (textSpread >= 1) return 0;
    // No action if textSpread 1 or more

    int offset = 0;

    // Do we need to pad BEFORE the next char?
    if (type == esaType.Pre | type == esaType.Either)
    {
        // Does our character appear in the "pre" list? (ie taller than average)
        if (" bdfhijkltABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".IndexOf(ch) > 0)
        {
            offset += (int)(font.Height * .2);
        }
    }

    // Do we need to pad AFTER the next char?
    if (type == esaType.Post | type == esaType.Either)
    {
        // Does our character appear in the "post" list?
        // (ie dangles over the bottom of the line)
        if (" gjpqyQ".IndexOf(ch) > 0)
        {
            offset += (int)(font.Height * .2);
        }
    }

    return offset;
}

The final important thing to mention is the textSpread value, which can be seen above in the third overload of the Draw method. This is a double which is used to determine the spacing of the string. Ignoring the effect of ExtraSpaceAllowance for now, if textSpread is set to 1, then each character will be positioned below the previous one by the exact height of the font. If textSpread is 0.5, then the characters' start positions will be half as far apart. The default for textSpread is 0.75, and it is exposed as a public property TextSpread.

Using the code

I haven't included a demo app or a DLL in the downloads section - all the demo does is display the screenshot at the top of the article, and the amount of code here hardly seemed worth assembling it into a separate file. Just copy the source for VerticalString into your project, and by all means create a DLL if you feel the need.

Unresolved issues?

If you cast a critical eye over the strings drawn by this class, you'll notice that although the vertical spacing is pretty good, certain letters of the alphabet seem to have a mind of their own when it comes to horizontal alignment. Look at the "Test String" 's on the screenshot at the top of the article - most of the characters are lined up quite nicely in a straight vertical line, but one or two are a little out of kilter. For example, the lower-case "s" and "t" seem strangely drawn to the right and the left respectively, and when one is above the other the problem is particularly apparent.

Maybe someone would like to write a new method similar to ExtraSpaceAllowance, to change the horizontal offset of charRect for certain characters. I thought about it, but was daunted by the fact that initial testing revealed this tendency for certain characters to list to port or starboard to be inconsistent across fonts - you have been warned!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Gary Perkin

United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
GeneralSimply using StringFormatFlags.DirectionVertical Pinmembergualo14-Mar-07 18:36 
GeneralRe: Simply using StringFormatFlags.DirectionVertical Pinmembergualo14-Mar-07 18:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 20 Jan 2005
Article Copyright 2005 by Gary Perkin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid