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

戏子无情 提交于 2019-12-04 05:21:21

问题


Trying to get string width in C# to simulate wordwrap and position of text (now written in richTextBox).

Size of richTextBox is 555x454 px and I use monospaced font Courier New 12pt.

I tried TextRenderer.MeasureText() and also Graphics.MeasureString() methods.

TextRenderer was returning bigger values than Graphics so text which normally fits into one line, my code determined should be wrapped to other line.

But with using Graphics, on the other hand, my code determined that particular string is shorter than it is printed in original richTextBox so it was wrapped to next line in wrong place.

During debugging I found out that computed widths differs, which is strange because I use monospaced font so widths should be same for all characters. But I get something like this from Graphics.MeasureString()(example.: ' ' - 5.33333254, 'S' - 15.2239571, '\r' - 5.328125).

How can I ACCURATELY compute string width with C# and so simulate word wrap and determine particular text positions in pixels?

Why is width different in different characters when using monospaced font?

Note: I am working on personal Eye tracking project and I want to determine, where particular pieces of text was placed during experiment so I can tell on which words was user looking. For ex. at time t user was looking on point [256,350]px and I know that at this place there is call of method WriteLine. My target visual stimulus is source code, with indents, tabs, line endings, placed in some editable text area (In the future maybe some simple online source code editor).

Here is my code:

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

回答1:


I believe that it is much easier to accomplish your task by obtaining a character under a given location on a screen. For example, if you're using the RichTextBox control, refer to the RichTextBox.GetCharIndexFromPosition method to get the index of the character nearest to the specified location. Here is some sample code that demonstrates the idea:

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



回答2:


So I finally added some things in my code. I will shorten it.

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

With this corrections I get correct width of common characters (with use of monospaced font i get equal widths).

But there is still problem with other whitespaces like \t and \n where I get 0.0078125 and 9.6015625 when measuring width with Courier New, 12pt font. The second value is a width of any character typed with this font so it is not a big problem but it would be better to be 0 or am I wrong? If anybody have a suggestion to solve this problem leave a comment please.



来源:https://stackoverflow.com/questions/42362999/strange-behavior-while-computing-string-width-in-pixels-for-simulation-of-word-w

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!