diff --git a/Directory.Build.props b/Directory.Build.props
index 0fc27e3e..c7cff62a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,8 +5,9 @@
0.10.999-cibuild0018389-beta
1.0.24
13.0.1
- 0.10.12.1
+ 0.10.12.1
+ https://api.nuget.org/v3/index.json;
https://nuget.avaloniaui.net/repository/avalonia-nightly/index.json;
diff --git a/src/AvaloniaEdit/Editing/LineNumberMargin.cs b/src/AvaloniaEdit/Editing/LineNumberMargin.cs
index 6bf71066..28353f9c 100644
--- a/src/AvaloniaEdit/Editing/LineNumberMargin.cs
+++ b/src/AvaloniaEdit/Editing/LineNumberMargin.cs
@@ -55,7 +55,7 @@ protected override Size MeasureOverride(Size availableSize)
Typeface = GetValue(TextBlock.FontFamilyProperty);
EmSize = GetValue(TextBlock.FontSizeProperty);
- var text = TextFormatterFactory.CreateFormattedText(
+ var text = TextFormatterFactory.CreateTextLine(
this,
new string('9', MaxLineNumberLength),
Typeface,
@@ -76,13 +76,13 @@ public override void Render(DrawingContext drawingContext)
foreach (var line in textView.VisualLines)
{
var lineNumber = line.FirstDocumentLine.LineNumber;
- var text = TextFormatterFactory.CreateFormattedText(
+ var text = TextFormatterFactory.CreateTextLine(
this,
lineNumber.ToString(CultureInfo.CurrentCulture),
Typeface, EmSize, foreground
);
var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop);
- drawingContext.DrawText(text,
+ text.Draw(drawingContext,
new Point(renderSize.Width - text.WidthIncludingTrailingWhitespace, y - textView.VerticalOffset));
}
}
diff --git a/src/AvaloniaEdit/Rendering/FormattedTextElement.cs b/src/AvaloniaEdit/Rendering/FormattedTextElement.cs
index 81fbd9b1..845fda21 100644
--- a/src/AvaloniaEdit/Rendering/FormattedTextElement.cs
+++ b/src/AvaloniaEdit/Rendering/FormattedTextElement.cs
@@ -31,7 +31,6 @@ namespace AvaloniaEdit.Rendering
///
public class FormattedTextElement : VisualLineElement
{
- internal FormattedText FormattedText { get; }
internal string Text { get; set; }
internal TextLine TextLine { get; set; }
@@ -53,15 +52,6 @@ internal FormattedTextElement(TextLine text, int documentLength) : base(1, docum
TextLine = text ?? throw new ArgumentNullException(nameof(text));
}
- ///
- /// Creates a new FormattedTextElement that displays the specified text
- /// and occupies the specified length in the document.
- ///
- public FormattedTextElement(FormattedText text, int documentLength) : base(1, documentLength)
- {
- FormattedText = text ?? throw new ArgumentNullException(nameof(text));
- }
-
///
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
@@ -131,11 +121,6 @@ public FormattedTextRun(FormattedTextElement element, TextRunProperties properti
public override Size GetSize(double remainingParagraphWidth)
{
- var formattedText = Element.FormattedText;
- if (formattedText != null)
- {
- return new Size(formattedText.WidthIncludingTrailingWhitespace, formattedText.Height);
- }
var text = Element.TextLine;
return new Size(text.WidthIncludingTrailingWhitespace,
text.Height);
@@ -150,16 +135,8 @@ public override Rect ComputeBoundingBox()
///
public override void Draw(DrawingContext drawingContext, Point origin)
{
- if (Element.FormattedText != null)
- {
- //origin = origin.WithY(origin.Y - Element.formattedText.Baseline);
- drawingContext.DrawText(Element.FormattedText, origin);
- }
- else
- {
- //origin.Y -= element.textLine.Baseline;
- Element.TextLine.Draw(drawingContext, origin);
- }
+ //origin.Y -= element.textLine.Baseline;
+ Element.TextLine.Draw(drawingContext, origin);
}
}
}
diff --git a/src/AvaloniaEdit/Text/StringRange.cs b/src/AvaloniaEdit/Text/StringRange.cs
index a27cf081..2eaa634f 100644
--- a/src/AvaloniaEdit/Text/StringRange.cs
+++ b/src/AvaloniaEdit/Text/StringRange.cs
@@ -21,6 +21,30 @@ public StringRange(string s, int offsetToFirstChar, int length)
Length = length;
}
+ public int IndexOf(char ch, int startIndex = 0)
+ {
+ for (int i = startIndex; i < Length; ++i)
+ {
+ if (ch == this[i])
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public StringRange SubRange(int start, int length)
+ {
+ if (start < 0)
+ throw new ArgumentOutOfRangeException(nameof(start));
+
+ if (start + length > Length)
+ throw new ArgumentOutOfRangeException(nameof(length));
+
+
+ return new StringRange(String, OffsetToFirstChar + start, length);
+ }
+
public override string ToString()
{
if (String == null) return string.Empty;
diff --git a/src/AvaloniaEdit/Text/TextLineRun.cs b/src/AvaloniaEdit/Text/TextLineRun.cs
index 2d71aa91..6abc2782 100644
--- a/src/AvaloniaEdit/Text/TextLineRun.cs
+++ b/src/AvaloniaEdit/Text/TextLineRun.cs
@@ -9,6 +9,7 @@
using Avalonia.Media.TextFormatting;
using AvaloniaEdit.Rendering;
+using AvaloniaEdit.Utils;
namespace AvaloniaEdit.Text
{
@@ -17,7 +18,7 @@ internal sealed class TextLineRun
private const string NewlineString = "\r\n";
private const string TabString = "\t";
- private FormattedText _formattedText;
+ private Avalonia.Media.TextFormatting.TextLine _formattedText;
private Size _formattedTextSize;
private IReadOnlyList _glyphWidths;
public StringRange StringRange { get; private set; }
@@ -68,7 +69,7 @@ public double Height
var box = embeddedObject.ComputeBoundingBox();
return box.Height;
}
-
+
return GetDefaultLineHeight(TextRun.Properties.FontMetrics);
}
}
@@ -178,33 +179,55 @@ private static TextLineRun CreateRunForTab(TextRun textRun, TextParagraphPropert
internal static TextLineRun CreateRunForText(StringRange stringRange, TextRun textRun, double widthLeft, bool emergencyWrap, bool breakOnTabs, TextParagraphProperties paragraphProperties)
{
- var run = new TextLineRun
+ var ngwdIdx = stringRange.IndexOf('\t');
+
+ StringRange text;
+ TextRun run;
+
+ if (ngwdIdx == -1)
{
- StringRange = stringRange,
- TextRun = textRun,
- Length = textRun.Length
+ text = stringRange;
+ run = textRun;
+ }
+ else
+ {
+
+ text = stringRange.SubRange(0, ngwdIdx);
+ run = new TextRunImpl(text, textRun.Properties);
+
+ }
+
+ var linerun = new TextLineRun
+ {
+ StringRange = text,
+ TextRun = run,
+ Length = run.Length
};
- var tf = run.Typeface;
- var formattedText = new FormattedText(stringRange.ToString(), CultureInfo.CurrentCulture,
- FlowDirection.LeftToRight, new Typeface(tf.FontFamily, tf.Style, tf.Weight), run.FontSize, textRun.Properties.ForegroundBrush);
+ var tf = linerun.Typeface;
+ var useCheapGlyphMeasurement =
+ run.Length >= VisualLine.LENGTH_LIMIT &&
+ tf.GlyphTypeface.IsFixedPitch;
- run._formattedText = formattedText;
- var size = new Size(formattedText.WidthIncludingTrailingWhitespace, formattedText.Height);
+ var line = TextFormatterFactory.CreateTextLine(
+ text.ToString(),
+ new Typeface(tf.FontFamily, tf.Style, tf.Weight),
+ linerun.FontSize,
+ run.Properties.ForegroundBrush);
- run._formattedTextSize = size;
+ linerun._formattedText = line;
- run.Width = size.Width;
+ var size = new Size(line.WidthIncludingTrailingWhitespace, line.Height);
- run._glyphWidths = new GlyphWidths(
- run.StringRange,
- run.Typeface.GlyphTypeface,
- run.FontSize,
- paragraphProperties.DefaultIncrementalTab);
+ linerun._formattedTextSize = size;
- return run;
+ linerun.Width = size.Width;
+
+ linerun._glyphWidths = new CharacterWidths(line, text.Length, useCheapGlyphMeasurement);
+
+ return linerun;
}
private TextLineRun(int length, TextRun textRun)
@@ -235,14 +258,14 @@ public void Draw(DrawingContext drawingContext, double x, double y)
drawingContext.FillRectangle(TextRun.Properties.BackgroundBrush, bounds);
}
- drawingContext.DrawText(_formattedText, new Point(x, y));
+ _formattedText.Draw(drawingContext, new Point(x, y));
var glyphTypeface = TextRun.Properties.Typeface.GlyphTypeface;
-
- var scale = TextRun.Properties.FontSize / glyphTypeface.DesignEmHeight;
+
+ var scale = TextRun.Properties.FontSize / glyphTypeface.DesignEmHeight;
var baseline = y + -glyphTypeface.Ascent * scale;
-
+
if (TextRun.Properties.Underline)
{
var pen = new Pen(TextRun.Properties.ForegroundBrush, glyphTypeface.UnderlineThickness * scale);
@@ -339,76 +362,85 @@ private static bool IsSpace(char ch)
return ch == ' ' || ch == '\u00a0';
}
- class GlyphWidths : IReadOnlyList
+ class CharacterWidths : IReadOnlyList
{
- private const double NOT_CALCULATED_YET = -1;
- private double[] _glyphWidths;
- private GlyphTypeface _typeFace;
- private StringRange _range;
- private double _scale;
- private double _tabSize;
-
- public int Count => _glyphWidths.Length;
- public double this[int index] => GetAt(index);
+ private Avalonia.Media.TextFormatting.TextLine _measure;
+ private double[] _width;
+ private bool _isMeasured;
- internal GlyphWidths(StringRange range, GlyphTypeface typeFace, double fontSize, double tabSize)
+ public CharacterWidths(Avalonia.Media.TextFormatting.TextLine measure, int length, bool useCheapGlyphMeasurement)
{
- _range = range;
- _typeFace = typeFace;
- _scale = fontSize / _typeFace.DesignEmHeight;
- _tabSize = tabSize;
+ _measure = measure;
+ _width = new double[length];
+ Count = length;
+ _isMeasured = false;
+
+ // TODO: This is not perfect implementation, It don't distinguish between sigle-byte width and double-byte width.
+ // This implementation is intended to reduce processing when very long characters are entered.
+ if (useCheapGlyphMeasurement)
+ {
+ _isMeasured = true;
- InitGlyphWidths();
+ var hit = _measure.GetNextCaretCharacterHit(new CharacterHit(0));
+ var firstCharWid = _measure.GetDistanceFromCharacterHit(hit);
+
+ for (var i = 0; i < Count; ++i)
+ _width[i] = firstCharWid;
+ }
}
- double GetAt(int index)
+
+ public double this[int index]
{
- if (_glyphWidths.Length == 0)
- return 0;
+ get
+ {
+ if (!_isMeasured)
+ {
+ var hit = new CharacterHit(0);
+ double prevPos = 0;
+ for (var i = 0; i < Count; ++i)
+ {
+ hit = _measure.GetNextCaretCharacterHit(hit);
+ var dist = _measure.GetDistanceFromCharacterHit(hit);
+ _width[i] = dist - prevPos;
- if (_range[index] == '\t')
- return _tabSize;
+ i = Math.Max(i, hit.FirstCharacterIndex - 1);
+ prevPos = dist;
+ }
- if (_glyphWidths[index] == NOT_CALCULATED_YET)
- _glyphWidths[index] = MeasureGlyphAt(index);
+ _isMeasured = true;
+ }
- return _glyphWidths[index];
+ return _width[index];
+ }
}
- double MeasureGlyphAt(int index)
+ public int Count { get; }
+
+ public IEnumerator GetEnumerator()
{
- return _typeFace.GetGlyphAdvance(
- _typeFace.GetGlyph(_range[index])) * _scale;
+ for (int i = 0; i < Count; ++i)
+ yield return this[i];
}
- void InitGlyphWidths()
- {
- int capacity = _range.Length;
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
- bool useCheapGlyphMeasurement =
- capacity >= VisualLine.LENGTH_LIMIT &&
- _typeFace.IsFixedPitch;
+ class TextRunImpl : TextRun
+ {
+ public sealed override StringRange StringRange { get; }
- if (useCheapGlyphMeasurement)
- {
- double size = MeasureGlyphAt(0);
- _glyphWidths = Enumerable.Repeat(size, capacity).ToArray();
- return;
- }
+ public sealed override int Length { get; }
- _glyphWidths = Enumerable.Repeat(NOT_CALCULATED_YET, capacity).ToArray();
- }
+ public sealed override TextRunProperties Properties { get; }
- IEnumerator IEnumerable.GetEnumerator()
- {
- foreach (double value in _glyphWidths)
- yield return value;
- }
- IEnumerator IEnumerable.GetEnumerator()
+ public TextRunImpl(StringRange stringRange, TextRunProperties textRunProperties)
{
- return _glyphWidths.GetEnumerator();
+ StringRange = stringRange;
+ Length = stringRange.Length;
+ Properties = textRunProperties;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/AvaloniaEdit/Utils/TextFormatterFactory.cs b/src/AvaloniaEdit/Utils/TextFormatterFactory.cs
index 6cf798be..fed01deb 100644
--- a/src/AvaloniaEdit/Utils/TextFormatterFactory.cs
+++ b/src/AvaloniaEdit/Utils/TextFormatterFactory.cs
@@ -28,39 +28,48 @@ namespace AvaloniaEdit.Utils
/// Creates TextFormatter instances that with the correct TextFormattingMode, if running on .NET 4.0.
///
public static class TextFormatterFactory
- {
- public static TextFormatter Create()
- {
- return new TextFormatter();
- }
+ {
+ public static TextFormatter Create()
+ {
+ return new TextFormatter();
+ }
- ///
- /// Creates formatted text.
- ///
- /// The owner element. The text formatter setting are read from this element.
- /// The text.
- /// The typeface to use. If this parameter is null, the typeface of the will be used.
- /// The font size. If this parameter is null, the font size of the will be used.
- /// The foreground color. If this parameter is null, the foreground of the will be used.
- /// A FormattedText object using the specified settings.
- public static FormattedText CreateFormattedText(Control element, string text, FontFamily typeface, double? emSize, IBrush foreground)
- {
- if (element == null)
- throw new ArgumentNullException(nameof(element));
- if (text == null)
- throw new ArgumentNullException(nameof(text));
- if (typeface == null)
- typeface = TextBlock.GetFontFamily(element);
- if (emSize == null)
- emSize = TextBlock.GetFontSize(element);
- if (foreground == null)
- foreground = TextBlock.GetForeground(element);
+ public static Avalonia.Media.TextFormatting.TextLine CreateTextLine(
+ string text,
+ Typeface typeface,
+ double fontSize,
+ IBrush foreground)
+ {
+ return new Avalonia.Media.TextFormatting.TextLayout(text, typeface, fontSize, foreground).TextLines[0];
+ }
- var formattedText = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
- new Typeface(typeface.Name), emSize.Value, foreground!);
-
+ ///
+ /// Creates text line.
+ ///
+ /// The owner element. The text formatter setting are read from this element.
+ /// The text.
+ /// The typeface to use. If this parameter is null, the typeface of the will be used.
+ /// The font size. If this parameter is null, the font size of the will be used.
+ /// The foreground color. If this parameter is null, the foreground of the will be used.
+ /// A FormattedText object using the specified settings.
+ public static Avalonia.Media.TextFormatting.TextLine CreateTextLine(
+ Control element,
+ string text,
+ FontFamily typeface, double? emSize,
+ IBrush foreground)
+ {
+ if (element == null)
+ throw new ArgumentNullException(nameof(element));
+ if (text == null)
+ throw new ArgumentNullException(nameof(text));
+ if (typeface == null)
+ typeface = TextBlock.GetFontFamily(element);
+ if (emSize == null)
+ emSize = TextBlock.GetFontSize(element);
+ if (foreground == null)
+ foreground = TextBlock.GetForeground(element);
- return formattedText;
- }
- }
+ return CreateTextLine(text, new Typeface(typeface.Name), emSize.Value, foreground!);
+ }
+ }
}