Browse Source

AvalonEdit perf optimization: Use a single TextLine for tab and space markers instead of creating FormattedText on demand.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@6169 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
pull/1/head
Daniel Grunwald 15 years ago
parent
commit
87f94f6e1b
  1. 12
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingElementGenerator.cs
  2. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  3. 93
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/FormattedTextElement.cs
  4. 13
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/NewLineElementGenerator.cs
  5. 43
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SimpleTextSource.cs
  6. 39
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs
  7. 22
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
  8. 41
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextViewCachedElements.cs
  9. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs

12
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingElementGenerator.cs

@ -64,13 +64,9 @@ namespace ICSharpCode.AvalonEdit.Folding @@ -64,13 +64,9 @@ namespace ICSharpCode.AvalonEdit.Folding
if (foldedUntil > offset) {
if (string.IsNullOrEmpty(title))
title = "...";
FormattedText text = TextFormatterFactory.CreateFormattedText(
CurrentContext.TextView,
title,
CurrentContext.GlobalTextRunProperties.Typeface,
CurrentContext.GlobalTextRunProperties.FontRenderingEmSize,
Brushes.Gray
);
var p = new VisualLineElementTextRunProperties(CurrentContext.GlobalTextRunProperties);
p.SetForegroundBrush(Brushes.Gray);
var text = FormattedTextElement.PrepareText(CurrentContext.TextView.TextFormatter, title, p);
return new FoldingLineElement(text, foldedUntil - offset);
} else {
return null;
@ -79,7 +75,7 @@ namespace ICSharpCode.AvalonEdit.Folding @@ -79,7 +75,7 @@ namespace ICSharpCode.AvalonEdit.Folding
sealed class FoldingLineElement : FormattedTextElement
{
public FoldingLineElement(FormattedText text, int documentLength) : base(text, documentLength)
public FoldingLineElement(TextLine text, int documentLength) : base(text, documentLength)
{
}

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -253,11 +253,15 @@ @@ -253,11 +253,15 @@
</Compile>
<Compile Include="Rendering\LinkElementGenerator.cs" />
<Compile Include="Rendering\NewLineElementGenerator.cs" />
<Compile Include="Rendering\SimpleTextSource.cs">
<DependentUpon>FormattedTextElement.cs</DependentUpon>
</Compile>
<Compile Include="Rendering\SingleCharacterElementGenerator.cs" />
<Compile Include="Rendering\TextLayer.cs">
<DependentUpon>TextView.cs</DependentUpon>
</Compile>
<Compile Include="Rendering\TextView.cs" />
<Compile Include="Rendering\TextViewCachedElements.cs" />
<Compile Include="Rendering\TextViewWeakEventManager.cs">
<DependentUpon>TextView.cs</DependentUpon>
</Compile>

93
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/FormattedTextElement.cs

@ -19,20 +19,23 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -19,20 +19,23 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// </summary>
public class FormattedTextElement : VisualLineElement
{
readonly FormattedText text;
internal readonly FormattedText formattedText;
internal string text;
internal TextLine textLine;
/// <summary>
/// Gets the formatted text.
/// </summary>
[Obsolete("For improved performance, FormattedTextElement should be used with TextLine instead of FormattedText.")]
public FormattedText Text {
get { return text; }
get { return formattedText; }
}
/// <summary>
/// Creates a new FormattedTextElement that displays the specified text
/// and occupies the specified length in the document.
/// </summary>
public FormattedTextElement(FormattedText text, int documentLength) : base(1, documentLength)
public FormattedTextElement(string text, int documentLength) : base(1, documentLength)
{
if (text == null)
throw new ArgumentNullException("text");
@ -41,6 +44,32 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -41,6 +44,32 @@ namespace ICSharpCode.AvalonEdit.Rendering
this.BreakAfter = LineBreakCondition.BreakPossible;
}
/// <summary>
/// Creates a new FormattedTextElement that displays the specified text
/// and occupies the specified length in the document.
/// </summary>
public FormattedTextElement(TextLine text, int documentLength) : base(1, documentLength)
{
if (text == null)
throw new ArgumentNullException("text");
this.textLine = text;
this.BreakBefore = LineBreakCondition.BreakPossible;
this.BreakAfter = LineBreakCondition.BreakPossible;
}
/// <summary>
/// Creates a new FormattedTextElement that displays the specified text
/// and occupies the specified length in the document.
/// </summary>
public FormattedTextElement(FormattedText text, int documentLength) : base(1, documentLength)
{
if (text == null)
throw new ArgumentNullException("text");
this.formattedText = text;
this.BreakBefore = LineBreakCondition.BreakPossible;
this.BreakAfter = LineBreakCondition.BreakPossible;
}
/// <summary>
/// Gets/sets the line break condition before the element.
/// The default is 'BreakPossible'.
@ -56,8 +85,35 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -56,8 +85,35 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// <inheritdoc/>
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
if (textLine == null) {
textLine = PrepareText(context.TextView.TextFormatter, this.text, this.TextRunProperties);
this.text = null;
}
return new FormattedTextRun(this, this.TextRunProperties);
}
/// <summary>
/// Constructs a TextLine from a simple text.
/// </summary>
public static TextLine PrepareText(TextFormatter formatter, string text, TextRunProperties properties)
{
if (formatter == null)
throw new ArgumentNullException("formatter");
if (text == null)
throw new ArgumentNullException("text");
if (properties == null)
throw new ArgumentNullException("properties");
return formatter.FormatLine(
new SimpleTextSource(text, properties),
0,
32000,
new VisualLineTextParagraphProperties {
defaultTextRunProperties = properties,
textWrapping = TextWrapping.NoWrap,
tabSize = 40
},
null);
}
}
/// <summary>
@ -121,22 +177,41 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -121,22 +177,41 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// <inheritdoc/>
public override TextEmbeddedObjectMetrics Format(double remainingParagraphWidth)
{
return new TextEmbeddedObjectMetrics(element.Text.WidthIncludingTrailingWhitespace,
element.Text.Height,
element.Text.Baseline);
var formattedText = element.formattedText;
if (formattedText != null) {
return new TextEmbeddedObjectMetrics(formattedText.WidthIncludingTrailingWhitespace,
formattedText.Height,
formattedText.Baseline);
} else {
var text = element.textLine;
return new TextEmbeddedObjectMetrics(text.WidthIncludingTrailingWhitespace,
text.Height,
text.Baseline);
}
}
/// <inheritdoc/>
public override Rect ComputeBoundingBox(bool rightToLeft, bool sideways)
{
return new Rect(0, 0, element.Text.WidthIncludingTrailingWhitespace, element.Text.Height);
var formattedText = element.formattedText;
if (formattedText != null) {
return new Rect(0, 0, formattedText.WidthIncludingTrailingWhitespace, formattedText.Height);
} else {
var text = element.textLine;
return new Rect(0, 0, text.WidthIncludingTrailingWhitespace, text.Height);
}
}
/// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways)
{
origin.Y -= element.Text.Baseline;
drawingContext.DrawText(element.Text, origin);
if (element.formattedText != null) {
origin.Y -= element.formattedText.Baseline;
drawingContext.DrawText(element.formattedText, origin);
} else {
origin.Y -= element.textLine.Baseline;
element.textLine.Draw(drawingContext, origin, InvertAxes.None);
}
}
}
}

13
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/NewLineElementGenerator.cs

@ -9,7 +9,7 @@ using System; @@ -9,7 +9,7 @@ using System;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Utils;
@ -58,19 +58,12 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -58,19 +58,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
} else {
return null;
}
FormattedText text = TextFormatterFactory.CreateFormattedText(
CurrentContext.TextView,
newlineText,
CurrentContext.GlobalTextRunProperties.Typeface,
CurrentContext.GlobalTextRunProperties.FontRenderingEmSize,
Brushes.LightGray
);
return new NewLineTextElement(text);
return new NewLineTextElement(CurrentContext.TextView.cachedElements.GetSimpleLightGrayText(newlineText, CurrentContext));
}
sealed class NewLineTextElement : FormattedTextElement
{
public NewLineTextElement(FormattedText text) : base(text, 0)
public NewLineTextElement(TextLine text) : base(text, 0)
{
BreakBefore = LineBreakCondition.BreakPossible;
BreakAfter = LineBreakCondition.BreakRestrained;

43
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SimpleTextSource.cs

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Diagnostics;
using System.Windows.Media.TextFormatting;
namespace ICSharpCode.AvalonEdit.Rendering
{
sealed class SimpleTextSource : TextSource
{
readonly string text;
readonly TextRunProperties properties;
public SimpleTextSource(string text, TextRunProperties properties)
{
this.text = text;
this.properties = properties;
}
public override TextRun GetTextRun(int textSourceCharacterIndex)
{
if (textSourceCharacterIndex < text.Length)
return new TextCharacters(text, textSourceCharacterIndex, text.Length - textSourceCharacterIndex, properties);
else
return new TextEndOfParagraph(1);
}
public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int textSourceCharacterIndex)
{
throw new NotImplementedException();
}
public override TextSpan<CultureSpecificCharacterBufferRange> GetPrecedingText(int textSourceCharacterIndexLimit)
{
throw new NotImplementedException();
}
}
}

39
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs

@ -92,31 +92,14 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -92,31 +92,14 @@ namespace ICSharpCode.AvalonEdit.Rendering
{
char c = CurrentContext.Document.GetCharAt(offset);
if (ShowSpaces && c == ' ') {
FormattedText text = TextFormatterFactory.CreateFormattedText(
CurrentContext.TextView,
"\u00B7",
CurrentContext.GlobalTextRunProperties.Typeface,
CurrentContext.GlobalTextRunProperties.FontRenderingEmSize,
Brushes.LightGray
);
return new SpaceTextElement(text);
return new SpaceTextElement(CurrentContext.TextView.cachedElements.GetSimpleLightGrayText("\u00B7", CurrentContext));
} else if (ShowTabs && c == '\t') {
FormattedText text = TextFormatterFactory.CreateFormattedText(
CurrentContext.TextView,
"\u00BB",
CurrentContext.GlobalTextRunProperties.Typeface,
CurrentContext.GlobalTextRunProperties.FontRenderingEmSize,
Brushes.LightGray
);
return new TabTextElement(text);
return new TabTextElement(CurrentContext.TextView.cachedElements.GetSimpleLightGrayText("\u00BB", CurrentContext));
} else if (ShowBoxForControlCharacters && char.IsControl(c)) {
FormattedText text = TextFormatterFactory.CreateFormattedText(
CurrentContext.TextView,
TextUtilities.GetControlCharacterName(c),
CurrentContext.GlobalTextRunProperties.Typeface,
CurrentContext.GlobalTextRunProperties.FontRenderingEmSize * 0.9,
Brushes.White
);
var p = new VisualLineElementTextRunProperties(CurrentContext.GlobalTextRunProperties);
p.SetForegroundBrush(Brushes.White);
var text = FormattedTextElement.PrepareText(CurrentContext.TextView.TextFormatter,
TextUtilities.GetControlCharacterName(c), p);
return new SpecialCharacterBoxElement(text);
} else {
return null;
@ -125,7 +108,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -125,7 +108,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
sealed class SpaceTextElement : FormattedTextElement
{
public SpaceTextElement(FormattedText text) : base(text, 1)
public SpaceTextElement(TextLine textLine) : base(textLine, 1)
{
BreakBefore = LineBreakCondition.BreakPossible;
BreakAfter = LineBreakCondition.BreakDesired;
@ -142,9 +125,9 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -142,9 +125,9 @@ namespace ICSharpCode.AvalonEdit.Rendering
sealed class TabTextElement : VisualLineElement
{
internal readonly FormattedText text;
internal readonly TextLine text;
public TabTextElement(FormattedText text) : base(2, 1)
public TabTextElement(TextLine text) : base(2, 1)
{
this.text = text;
}
@ -222,13 +205,13 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -222,13 +205,13 @@ namespace ICSharpCode.AvalonEdit.Rendering
public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways)
{
origin.Y -= element.text.Baseline;
drawingContext.DrawText(element.text, origin);
element.text.Draw(drawingContext, origin, InvertAxes.None);
}
}
sealed class SpecialCharacterBoxElement : FormattedTextElement
{
public SpecialCharacterBoxElement(FormattedText text) : base(text, 1)
public SpecialCharacterBoxElement(TextLine text) : base(text, 1)
{
}

22
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs

@ -104,6 +104,8 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -104,6 +104,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
heightTree = null;
formatter.Dispose();
formatter = null;
cachedElements.Dispose();
cachedElements = null;
TextDocumentWeakEventManager.Changing.RemoveListener(oldValue, this);
}
this.document = newValue;
@ -113,6 +115,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -113,6 +115,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
TextDocumentWeakEventManager.Changing.AddListener(newValue, this);
heightTree = new HeightTree(newValue, FontSize + 3);
formatter = TextFormatterFactory.Create(this);
cachedElements = new TextViewCachedElements();
}
InvalidateMeasure(DispatcherPriority.Normal);
if (DocumentChanged != null)
@ -132,6 +135,14 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -132,6 +135,14 @@ namespace ICSharpCode.AvalonEdit.Rendering
}
}
void RecreateCachedElements()
{
if (cachedElements != null) {
cachedElements.Dispose();
cachedElements = new TextViewCachedElements();
}
}
/// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
@ -764,6 +775,15 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -764,6 +775,15 @@ namespace ICSharpCode.AvalonEdit.Rendering
#region BuildVisualLine
TextFormatter formatter;
internal TextViewCachedElements cachedElements;
/// <summary>
/// Gets the TextFormatter used by the text view.
/// Returns null if no document is assigned to the text view.
/// </summary>
public TextFormatter TextFormatter {
get { return formatter; }
}
TextRunProperties CreateGlobalTextRunProperties()
{
@ -1557,6 +1577,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -1557,6 +1577,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
{
base.OnPropertyChanged(e);
if (TextFormatterFactory.PropertyChangeAffectsTextFormatter(e.Property)) {
RecreateCachedElements();
RecreateTextFormatter();
}
if (e.Property == Control.ForegroundProperty
@ -1566,6 +1587,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -1566,6 +1587,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
|| e.Property == Control.FontStyleProperty
|| e.Property == Control.FontWeightProperty)
{
RecreateCachedElements();
Redraw();
}
}

41
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextViewCachedElements.cs

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
namespace ICSharpCode.AvalonEdit.Rendering
{
sealed class TextViewCachedElements : IDisposable
{
Dictionary<string, TextLine> simpleLightGrayTexts;
public TextLine GetSimpleLightGrayText(string text, ITextRunConstructionContext context)
{
if (simpleLightGrayTexts == null)
simpleLightGrayTexts = new Dictionary<string, TextLine>();
TextLine textLine;
if (!simpleLightGrayTexts.TryGetValue(text, out textLine)) {
var p = new VisualLineElementTextRunProperties(context.GlobalTextRunProperties);
p.SetForegroundBrush(Brushes.LightGray);
textLine = FormattedTextElement.PrepareText(context.TextView.TextFormatter, text, p);
simpleLightGrayTexts[text] = textLine;
}
return textLine;
}
public void Dispose()
{
if (simpleLightGrayTexts != null) {
foreach (TextLine line in simpleLightGrayTexts.Values)
line.Dispose();
}
}
}
}

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs

@ -16,7 +16,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -16,7 +16,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// <summary>
/// WPF TextSource implementation that creates TextRuns for a VisualLine.
/// </summary>
class VisualLineTextSource : TextSource, ITextRunConstructionContext
sealed class VisualLineTextSource : TextSource, ITextRunConstructionContext
{
public VisualLineTextSource(VisualLine visualLine)
{

Loading…
Cancel
Save