Странное поведение при вычислении ширины строки в пикселях для симуляции переноса слов
Попытка получить ширину строки в 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();
}