以像素为单位计算字符串宽度以模拟自动换行时的奇怪行为

Strange behavior while computing string width in pixels for simulation of word wrap

尝试在 C# 中获取字符串宽度以模拟文本的自动换行和位置(现在用 richTextBox 编写)。

richTextBox 的大小为 555x454 像素,我使用等宽字体 Courier New 12pt。

我尝试了 TextRenderer.MeasureText()Graphics.MeasureString() 方法。

TextRenderer 返回的值比 Graphics 大,所以通常适合一行的文本,我确定的代码应该换行。

但是另一方面,使用 Graphics 时,我的代码确定特定字符串比原始 richTextBox 中打印的字符串短,因此它被换行到错误位置的下一行。

在调试过程中,我发现计算出的宽度不同,这很奇怪,因为我使用等宽字体,所以所有字符的宽度应该相同。但我从 Graphics.MeasureString()(例如:' ' - 5.33333254,'S' - 15.2239571,'\r' - 5.328125)得到类似的东西。

如何使用 C# 准确计算字符串宽度,从而模拟自动换行并确定以像素为单位的特定文本位置?

为什么使用等宽字体时不同字符的宽度不同?

注意:我正在进行个人眼动追踪项目,我想确定在实验期间放置特定文本的位置,以便我可以判断用户在看哪些词。对于前。在时间 t 用户正在查看点 [256,350]px,我知道在这个地方有方法调用 WriteLine。 我的目标视觉刺激是源代码,带有缩进、制表符、行尾,放置在一些可编辑的文本区域(将来可能是一些简单的在线源代码编辑器)。

这是我的代码:

    //before method call
    var font = new Font("Courier New", 12, GraphicsUnit.Point);
    var graphics = this.CreateGraphics();
    var wrapped = sourceCode.WordWrap(font, 555, graphics);

    public static List<string> WordWrap(this string sourceCode, Font font, int width, Graphics g)
        {
            var wrappedText = new List<string>(); // output
            var actualLine = new StringBuilder();
            var actualWidth = 0.0f; // temp var for computing actual string length
            var lines = Regex.Split(sourceCode, @"(?<=\r\n)"); // split input to lines and maintain line ending \r\n where they are
            string[] wordsOfLine;

            foreach (var line in lines)
            {
                wordsOfLine = Regex.Split(line, @"( |\t)").Where(s => !s.Equals("")).ToArray(); // split line by tabs and spaces and maintain delimiters separately

                foreach (string word in wordsOfLine)
                {
                    var wordWidth = g.MeasureString(word, font).Width; // compute width of word

                    if (actualWidth + wordWidth > width) // if actual line width is grather than width of text area
                    {
                        wrappedText.Add(actualLine.ToString()); // add line to list
                        actualLine.Clear(); // clear StringBuilder
                        actualWidth = 0; // zero actual line width
                    }

                    actualLine.Append(word); // add word to actual line
                    actualWidth += wordWidth; // add word width to actual line width
                }

                if (actualLine.Length > 0) // if there is something in actual line add it to list
                {
                    wrappedText.Add(actualLine.ToString());
                }
                actualLine.Clear(); // clear vars
                actualWidth = 0;
            }

            return wrappedText;
        }

我相信通过获取屏幕上给定位置下的角色来完成您的任务要容易得多。例如,如果您使用的是 RichTextBox 控件,请参考 RichTextBox.GetCharIndexFromPosition 方法来获取离指定位置最近的字符的索引。下面是一些演示该想法的示例代码:

private void richTextBox1_MouseMove(object sender, MouseEventArgs e)
{
    var textIndex = richTextBox1.GetCharIndexFromPosition(e.Location);
    if (richTextBox1.Text.Length > 0)
        label1.Text = richTextBox1.Text[textIndex].ToString();
}

所以我终于在我的代码中添加了一些东西。我会缩短它。

public static List<string> WordWrap(this string sourceCode, Font font, int width, Graphics g)
    {
        g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
        var format = StringFormat.GenericTypographic;
        format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;

        var width = g.MeasureString(word, font, 0, format).Width;
    }

通过这个更正,我得到了正确的常用字符宽度(使用等宽字体我得到了相等的宽度)。

但是其他空格仍然存在问题,例如 \t\n,在使用 Courier New,12pt 字体测量宽度时,我得到 0.00781259.6015625。第二个值是用这种字体输入的任何字符的宽度,所以这不是什么大问题,但最好是 0 还是我错了?如果有人有解决这个问题的建议请发表评论。