Browse Source

Handle extra lines during hit testing

pull/11667/head
Benedikt Stebner 3 years ago
parent
commit
cc507ba359
  1. 13
      src/Avalonia.Base/Media/GlyphRun.cs
  2. 2
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  3. 22
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  4. 18
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  5. 15
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

13
src/Avalonia.Base/Media/GlyphRun.cs

@ -643,12 +643,13 @@ namespace Avalonia.Media
lastCluster = _glyphInfos[_glyphInfos.Count - 1].GlyphCluster;
}
var isReversed = firstCluster > lastCluster;
if (!IsLeftToRight)
{
(lastCluster, firstCluster) = (firstCluster, lastCluster);
}
var isReversed = firstCluster > lastCluster;
var height = GlyphTypeface.Metrics.LineSpacing * Scale;
var widthIncludingTrailingWhitespace = 0d;
@ -766,15 +767,13 @@ namespace Avalonia.Media
if (!charactersSpan.IsEmpty)
{
var characterIndex = 0;
var characterIndex = charactersSpan.Length - 1;
for (var i = 0; i < _glyphInfos.Count; i++)
{
var currentCluster = _glyphInfos[i].GlyphCluster;
var codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out var characterLength);
characterIndex += characterLength;
if (!codepoint.IsWhiteSpace)
{
break;
@ -784,9 +783,9 @@ namespace Avalonia.Media
var j = i;
while (j - 1 >= 0)
while (j + 1 < _glyphInfos.Count)
{
var nextCluster = _glyphInfos[--j].GlyphCluster;
var nextCluster = _glyphInfos[++j].GlyphCluster;
if (currentCluster == nextCluster)
{
@ -798,6 +797,8 @@ namespace Avalonia.Media
break;
}
characterIndex -= clusterLength;
if (codepoint.IsBreakChar)
{
newLineLength += clusterLength;

2
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -684,7 +684,9 @@ namespace Avalonia.Media.TextFormatting
var textRuns = new TextRun[] { new ShapedTextRun(shapedBuffer, properties) };
var line = new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection);
line.FinalizeLine();
return line;
}

22
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -128,7 +128,7 @@ namespace Avalonia.Media.TextFormatting
/// <summary>
/// Gets the text spacing.
/// </summary>
public double LetterSpacing => _paragraphProperties.LetterSpacing;
public double LetterSpacing => _paragraphProperties.LetterSpacing;
/// <summary>
/// Gets the text lines.
@ -271,11 +271,13 @@ namespace Avalonia.Media.TextFormatting
var currentY = 0.0;
foreach (var textLine in _textLines)
for (var i = 0; i < _textLines.Length; i++)
{
var textLine = _textLines[i];
var end = textLine.FirstTextSourceIndex + textLine.Length;
if (end <= textPosition && end < _textSourceLength)
if (end <= textPosition && i + 1 < _textLines.Length)
{
currentY += textLine.Height;
@ -511,7 +513,7 @@ namespace Avalonia.Media.TextFormatting
{
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first,
UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first,
ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom);
return new TextLine[] { textLine };
@ -638,13 +640,13 @@ namespace Avalonia.Media.TextFormatting
}
private void UpdateMetrics(
TextLine currentLine,
ref double lineStartOfLongestLine,
ref Point origin,
ref bool first,
TextLine currentLine,
ref double lineStartOfLongestLine,
ref Point origin,
ref bool first,
ref double accBlackBoxLeft,
ref double accBlackBoxTop,
ref double accBlackBoxRight,
ref double accBlackBoxTop,
ref double accBlackBoxRight,
ref double accBlackBoxBottom)
{
var blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading;

18
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -640,14 +640,14 @@ namespace Avalonia.Media.TextFormatting
bool TryMergeWithLastBounds(TextBounds currentBounds, TextBounds lastBounds)
{
if(currentBounds.FlowDirection != lastBounds.FlowDirection)
if (currentBounds.FlowDirection != lastBounds.FlowDirection)
{
return false;
}
if(currentBounds.Rectangle.Left == lastBounds.Rectangle.Right)
if (currentBounds.Rectangle.Left == lastBounds.Rectangle.Right)
{
foreach(var runBounds in currentBounds.TextRunBounds)
foreach (var runBounds in currentBounds.TextRunBounds)
{
lastBounds.TextRunBounds.Add(runBounds);
}
@ -657,7 +657,7 @@ namespace Avalonia.Media.TextFormatting
return true;
}
if(currentBounds.Rectangle.Right == lastBounds.Rectangle.Left)
if (currentBounds.Rectangle.Right == lastBounds.Rectangle.Left)
{
for (int i = 0; i < currentBounds.TextRunBounds.Count; i++)
{
@ -730,7 +730,7 @@ namespace Avalonia.Media.TextFormatting
if (coveredLength > 0)
{
if(lastBounds != null && TryMergeWithLastBounds(currentBounds, lastBounds))
if (lastBounds != null && TryMergeWithLastBounds(currentBounds, lastBounds))
{
currentBounds = lastBounds;
@ -739,7 +739,7 @@ namespace Avalonia.Media.TextFormatting
else
{
result.Add(currentBounds);
}
}
lastBounds = currentBounds;
@ -1002,14 +1002,14 @@ namespace Avalonia.Media.TextFormatting
public void FinalizeLine()
{
_indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex);
_textLineMetrics = CreateLineMetrics();
if (_textLineBreak is null && _textRuns.Length > 1 && _textRuns[_textRuns.Length - 1] is TextEndOfLine textEndOfLine)
{
_textLineBreak = new TextLineBreak(textEndOfLine);
}
_indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex);
}
}
/// <summary>

15
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -1112,6 +1112,21 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_HitTestTextPosition_EndOfLine_RTL()
{
var text = "גש\r\n";
using (Start())
{
var textLayout = new TextLayout(text, Typeface.Default, 12, Brushes.Black, flowDirection: FlowDirection.RightToLeft);
var rect = textLayout.HitTestTextPosition(text.Length);
Assert.Equal(14.0625, rect.Top);
}
}
private static IDisposable Start()
{

Loading…
Cancel
Save