65.9K
CodeProject 正在变化。 阅读更多。
Home

垂直文本

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.59/5 (9投票s)

2005年1月20日

5分钟阅读

viewsIcon

70724

downloadIcon

755

一个用于绘制垂直文本字符串的 C# 类。

Sample Image - Vertical_Text.jpg

引言

在做一个 Button UserControl 项目时,我需要编写垂直文本。我在网上搜索了常用的方法,但发现的都只是如何将整个字符串旋转 90 度或 270 度。我想要的是将字符串垂直绘制,但保持字符 upright。一无所获后,我决定自己动手,并编写了 VerticalText 类。

遇到的问题

我的第一次尝试是相当成功的,而且并不特别困难。但是,字符串有点“散开”,如下图截图中最左边的字符串所示。

正如你所见,“i”可能会被原谅,因为它们可能会怀疑自己是否遇到了个人卫生问题,而底部的“s”似乎正试图逃往下一个词…

在第二次尝试中,我应用了一个 TextSpread 值(稍后讨论),以减小每个字符的高度。这对于“s”和“i”来说没问题,但对于较高的字符或带尾巴的字符就不那么好了。当一个高大的字符跟在一个带尾巴的字符后面时,例如“gh”,问题就会加剧,字符会开始重叠。

为了解决这个问题,我开发了一个小例程,根据所讨论的字符,它会返回一个小的偏移量,在写入字符之前或之后应用。结果是右边的字符串,至少在我看来,看起来还不错。

代码

VerticalText 类中的主要方法是 Draw。此方法有三个重载,如下所示。(前两个重载最终调用最后一个。)

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

第二个重载根据 StringFormatAlignmentLineAlignment 属性计算文本的起始坐标。

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 由矩形的宽度和字体的宽度决定。Y 基于矩形的高度和字符串的垂直长度,该长度由 Length 方法计算。

[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;
}

Length 方法是 public 的,因此调用代码可以使用它。这很有用,例如在检查要绘制的字符串是否适合您想绘制的位置时。

Draw 方法的第三个重载(由前两个调用)完成了绘制每个单独字符的主要工作,从 xy 指定的位置开始,该位置由调用程序或前两个重载之一传递。此代码片段显示了重要部分。

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

一个小矩形位于提供的 x 和 y 坐标处,其宽度和高度基于字体。使用了另一个 StringFormat 实例,这次是为了将每个字符定位在矩形内。然后,我们遍历字符串中的每个字符,该字符串现在表示为 textChars Array。在绘制每个字符之前和之后,我们调用 ExtraSpaceAllowance 方法。

如果您一直认真阅读,您会记得我们在 Length 方法中遇到了 ExtraSpaceAllowance,尽管我们并没有正式介绍它。此方法接受 esaType、一个 char 和一个 Font 作为参数。esaType 告诉我们是处理“高”字符(esaType.Pre)、“尾巴”字符(esaType.Post)还是两者。一旦我们知道我们在做什么,我们就会在适当的限定字符字符串中查找传入的字符。如果找到它,我们将返回一个 int,给出要添加的额外像素量。这被设置为字体高度的五分之一——这个数字是通过反复试验得出的,而且似乎效果不错。

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

最后一件重要的事情是 textSpread 值,您可以在上面 Draw 方法的第三个重载中看到它。这是一个 double,用于确定字符串的间距。暂时忽略 ExtraSpaceAllowance 的影响,如果 textSpread 设置为 1,则每个字符将位于前一个字符下方,间距恰好为字体高度。如果 textSpread 为 0.5,则字符的起始位置将相距一半。textSpread 的默认值为 0.75,并将其公开为 public 属性 TextSpread

使用代码

我没有在下载部分包含演示应用程序或 DLL - 所有演示只是显示文章顶部的截图,而这里的代码量几乎不值得将其组装成一个单独的文件。只需将 VerticalString 的源代码复制到您的项目中,如果您觉得需要,也可以创建一个 DLL。

未解决的问题?

如果您仔细查看此类绘制的字符串,您会注意到,尽管垂直间距相当好,但字母表中的某些字母在水平对齐方面似乎有自己的想法。查看文章顶部截图中的“Test String”的“s”,大部分字符都很好地对齐成一条直线,但有一两个有点跑偏。例如,小写的“s”和“t”分别显得向右和向左绘制,当一个字符在另一个字符上方时,问题尤为明显。

也许有人想编写一个类似于 ExtraSpaceAllowance 的新方法,来改变 charRect 对某些字符的水平偏移。我考虑过,但被这样一个事实吓倒了:初步测试显示,某些字符倾向于向右舷或左舷倾斜的趋势因字体而异——警告您了!

© . All rights reserved.