Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the ussage of FormattedText to TextLine #1

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
<AvaloniaVersion>0.10.999-cibuild0018389-beta</AvaloniaVersion>
<TextMateSharpVersion>1.0.24</TextMateSharpVersion>
<NewtonsoftJsonVersion>13.0.1</NewtonsoftJsonVersion>
<Version>0.10.12.1</Version>
<Version>0.10.12.1</Version>
<RestoreSources>
https://api.nuget.org/v3/index.json;
https://nuget.avaloniaui.net/repository/avalonia-nightly/index.json;
</RestoreSources>
</PropertyGroup>
Expand Down
6 changes: 3 additions & 3 deletions src/AvaloniaEdit/Editing/LineNumberMargin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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));
}
}
Expand Down
27 changes: 2 additions & 25 deletions src/AvaloniaEdit/Rendering/FormattedTextElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ namespace AvaloniaEdit.Rendering
/// </summary>
public class FormattedTextElement : VisualLineElement
{
internal FormattedText FormattedText { get; }
internal string Text { get; set; }
internal TextLine TextLine { get; set; }

Expand All @@ -53,15 +52,6 @@ internal FormattedTextElement(TextLine text, int documentLength) : base(1, docum
TextLine = text ?? throw new ArgumentNullException(nameof(text));
}

/// <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)
{
FormattedText = text ?? throw new ArgumentNullException(nameof(text));
}

/// <inheritdoc/>
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
Expand Down Expand Up @@ -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);
Expand All @@ -150,16 +135,8 @@ public override Rect ComputeBoundingBox()
/// <inheritdoc/>
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);
}
}
}
24 changes: 24 additions & 0 deletions src/AvaloniaEdit/Text/StringRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
176 changes: 104 additions & 72 deletions src/AvaloniaEdit/Text/TextLineRun.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Avalonia.Media.TextFormatting;

using AvaloniaEdit.Rendering;
using AvaloniaEdit.Utils;

namespace AvaloniaEdit.Text
{
Expand All @@ -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<double> _glyphWidths;
public StringRange StringRange { get; private set; }
Expand Down Expand Up @@ -68,7 +69,7 @@ public double Height
var box = embeddedObject.ComputeBoundingBox();
return box.Height;
}

return GetDefaultLineHeight(TextRun.Properties.FontMetrics);
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -339,76 +362,85 @@ private static bool IsSpace(char ch)
return ch == ' ' || ch == '\u00a0';
}

class GlyphWidths : IReadOnlyList<double>
class CharacterWidths : IReadOnlyList<double>
{
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<double> 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<double>(size, capacity).ToArray();
return;
}
public sealed override int Length { get; }

_glyphWidths = Enumerable.Repeat<double>(NOT_CALCULATED_YET, capacity).ToArray();
}
public sealed override TextRunProperties Properties { get; }

IEnumerator<double> IEnumerable<double>.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;
}
}
}
}
}
Loading