垂直文本






3.59/5 (9投票s)
2005年1月20日
5分钟阅读

70724

755
一个用于绘制垂直文本字符串的 C# 类。
引言
在做一个 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);
第二个重载根据 StringFormat
的 Alignment
和 LineAlignment
属性计算文本的起始坐标。
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
方法的第三个重载(由前两个调用)完成了绘制每个单独字符的主要工作,从 x
和 y
指定的位置开始,该位置由调用程序或前两个重载之一传递。此代码片段显示了重要部分。
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
对某些字符的水平偏移。我考虑过,但被这样一个事实吓倒了:初步测试显示,某些字符倾向于向右舷或左舷倾斜的趋势因字体而异——警告您了!