Странное поведение при вычислении ширины строки в пикселях для симуляции переноса слов

Попытка получить ширину строки в C# для имитации переноса слов и положения текста (теперь написано в richTextBox).

Размер richTextBox составляет 555x454 px, и я использую моноширинный шрифт 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;
        }

2 ответа

Решение

Итак, я наконец добавил некоторые вещи в свой код. Я сокращу это.

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 где я получаю 0.0078125 а также 9.6015625 при измерении ширины с помощью шрифта Courier New, 12pt. Второе значение - это ширина любого символа, набранного этим шрифтом, так что это не большая проблема, но было бы лучше иметь значение 0, или я ошибаюсь? Если у кого-то есть предложение решить эту проблему, оставьте комментарий, пожалуйста.

Я считаю, что намного проще выполнить вашу задачу, получив персонажа в заданном месте на экране. Например, если вы используете элемент управления 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();
}
Другие вопросы по тегам