patterncsharpMinor
Can calculation of word wrapping be made notably faster?
Viewed 0 times
cancalculationmadefasterwordwrappingnotably
Problem
I need to re-wrap a text so that it fits a given width, font and DC. The result needs to be an array of lines. I use the following code:
Performance measurement shows that
private struct SplitInfo
{
public string Word;
public string SplitChar;
public override string ToString()
{
return Word;
}
}
private static float GetWidth(Graphics gr, string text, Font font)
{
SizeF size = gr.MeasureString(text, font, 10000, StringFormat.GenericTypographic);
return size.Width;
}
public static string[] WrapText(string text, IntPtr hdc, Font font, int textWidthInLoMetric)
{
// Split words at space (functionality for splitting at multiple different chars omitted for simplicity).
// This is also why it needs SplitInfo to store the words.
List words = new List(text.Split(' ').Select(x => new SplitInfo { SplitChar = " ", Word = x }));
StringBuilder resultText = new StringBuilder();
string currentLine = string.Empty;
SplitInfo lastWord = new SplitInfo { Word = string.Empty, SplitChar = string.Empty };
using (Graphics gr = Graphics.FromHdc(hdc))
{
while (true)
{
string newString = (currentLine + lastWord.SplitChar).TrimStart(' ') + words[0].Word;
if (currentLine != string.Empty && GetWidth(gr, newString, font) > textWidthInLoMetric)
{
// Word no longer fits in line.
resultText.Append(currentLine.TrimEnd() + "\n");
currentLine = string.Empty;
}
else
{
lastWord = words[0];
words.RemoveAt(0);
currentLine = newString;
}
if (words.Count == 0)
{
resultText.Append(currentLine.TrimEnd() + "\n");
break;
}
}
}
return resultText.ToString().TrimEnd().Split('\n');
}Performance measurement shows that
MeasureString is the main time consumer here (~90%). Is there any clever Solution
One possibility is to cache the measured length for each word. I do this, when the line breaks need to be recalculated (i.e. when document needs to be reflowed) frequently, for example because the user edits words or the user resizes the width of the control.
A second possibility is to use different APIs, for example:
My code says ...
... and ...
This code recalculates dozens of pages in a fraction of a second (and because it caches the measured word sizes it only measures all words once, when the document is first loaded).
The corresponding method which I use for drawing the text is:
A second possibility is to use different APIs, for example:
- A different
StringFormatvalue passed as a parameter
- A different method for example
TextRenderer.MeasureTextinstead ofGraphics.MeasureString
My code says ...
//http://msdn.microsoft.com/en-us/magazine/cc163630.aspx#S7 i.e.
//"Practical Tips For Boosting The Performance Of Windows Forms Apps" in MSDN says,
//
//It's better to use the TextRenderer method overloads that do not get IDeviceContext as an argument.
//These methods are more efficient because they use a cached screen-compatible memory device context
//rather than retrieving the native handle for device context from the internal DeviceContext and
//creating an internal object to wrap it.
//
//My profiling shows that the method without the Graphics parameter takes 0.025 msec instead of 0.1 msec.
//However, the resulting calculation is inaccurate.
Size size = TextRenderer.MeasureText(
m_graphics,
text,
paintStyle.font,
proposedSize,
textFormatFlags);... and ...
static TextFormatFlags textFormatFlags =
TextFormatFlags.NoPrefix |
TextFormatFlags.NoPadding |
TextFormatFlags.ExternalLeading |
TextFormatFlags.Left |
TextFormatFlags.Bottom;This code recalculates dozens of pages in a fraction of a second (and because it caches the measured word sizes it only measures all words once, when the document is first loaded).
The corresponding method which I use for drawing the text is:
TextRenderer.DrawText(
m_graphics,
text,
paintStyle.font,
drawingPoint,
color,
textFormatFlags);Code Snippets
//http://msdn.microsoft.com/en-us/magazine/cc163630.aspx#S7 i.e.
//"Practical Tips For Boosting The Performance Of Windows Forms Apps" in MSDN says,
//
//It's better to use the TextRenderer method overloads that do not get IDeviceContext as an argument.
//These methods are more efficient because they use a cached screen-compatible memory device context
//rather than retrieving the native handle for device context from the internal DeviceContext and
//creating an internal object to wrap it.
//
//My profiling shows that the method without the Graphics parameter takes 0.025 msec instead of 0.1 msec.
//However, the resulting calculation is inaccurate.
Size size = TextRenderer.MeasureText(
m_graphics,
text,
paintStyle.font,
proposedSize,
textFormatFlags);static TextFormatFlags textFormatFlags =
TextFormatFlags.NoPrefix |
TextFormatFlags.NoPadding |
TextFormatFlags.ExternalLeading |
TextFormatFlags.Left |
TextFormatFlags.Bottom;TextRenderer.DrawText(
m_graphics,
text,
paintStyle.font,
drawingPoint,
color,
textFormatFlags);Context
StackExchange Code Review Q#46718, answer score: 5
Revisions (0)
No revisions yet.