Browse Source

More hit testing fixes for embedded content runs

pull/7946/head
Benedikt Stebner 4 years ago
parent
commit
005907a93b
  1. 61
      src/Avalonia.Base/Media/GlyphRun.cs
  2. 24
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  3. 44
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  4. 3
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  5. 21
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

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

@ -28,6 +28,8 @@ namespace Avalonia.Media
private IReadOnlyList<Vector>? _glyphOffsets;
private IReadOnlyList<int>? _glyphClusters;
private int _offsetToFirstCharacter;
/// <summary>
/// Initializes a new instance of the <see cref="GlyphRun"/> class by specifying properties of the class.
/// </summary>
@ -203,7 +205,7 @@ namespace Avalonia.Media
/// </returns>
public double GetDistanceFromCharacterHit(CharacterHit characterHit)
{
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength - _offsetToFirstCharacter;
var distance = 0.0;
@ -552,30 +554,20 @@ namespace Avalonia.Media
}
nextCluster = GlyphClusters[currentIndex];
}
if (nextCluster < Characters.Start)
{
nextCluster = Characters.Start;
}
if (cluster < Characters.Start)
{
cluster = Characters.Start;
}
}
int trailingLength;
if (nextCluster == cluster)
{
trailingLength = Characters.Start + Characters.Length - cluster;
trailingLength = Characters.Start + Characters.Length - _offsetToFirstCharacter - cluster;
}
else
{
trailingLength = nextCluster - cluster;
}
return new CharacterHit(cluster, trailingLength);
return new CharacterHit(_offsetToFirstCharacter + cluster, trailingLength);
}
/// <summary>
@ -609,6 +601,13 @@ namespace Avalonia.Media
private GlyphRunMetrics CreateGlyphRunMetrics()
{
if (GlyphClusters != null && GlyphClusters.Count > 0)
{
var firstCluster = GlyphClusters[0];
_offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster);
}
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
var widthIncludingTrailingWhitespace = 0d;
@ -680,34 +679,40 @@ namespace Avalonia.Media
{
for (var i = GlyphClusters.Count - 1; i >= 0; i--)
{
var cluster = GlyphClusters[i];
var codepointIndex = IsLeftToRight ? cluster - _characters.Start : _characters.End - cluster;
var currentCluster = GlyphClusters[i];
var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset);
var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _);
if (codepointIndex < 0)
if (!codepoint.IsWhiteSpace)
{
trailingWhitespaceLength = _characters.Length;
glyphCount = GlyphClusters.Count;
break;
}
var codepoint = Codepoint.ReadAt(_characters, codepointIndex, out _);
var clusterLength = 1;
if (!codepoint.IsWhiteSpace)
while(i - 1 >= 0)
{
var nextCluster = GlyphClusters[i - 1];
if(currentCluster == nextCluster)
{
clusterLength++;
i--;
continue;
}
break;
}
if (codepoint.IsBreakChar)
{
newLineLength++;
newLineLength += clusterLength;
}
trailingWhitespaceLength++;
glyphCount++;
trailingWhitespaceLength += clusterLength;
glyphCount++;
}
}

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

@ -79,14 +79,14 @@ namespace Avalonia.Media.TextFormatting
{
var currentRun = textRuns[i];
if (currentLength + currentRun.Text.Length < length)
if (currentLength + currentRun.TextSourceLength < length)
{
currentLength += currentRun.TextSourceLength;
continue;
}
var firstCount = currentRun.Text.Length >= 1 ? i + 1 : i;
var firstCount = currentRun.TextSourceLength >= 1 ? i + 1 : i;
var first = new List<DrawableTextRun>(firstCount);
@ -100,13 +100,13 @@ namespace Avalonia.Media.TextFormatting
var secondCount = textRuns.Count - firstCount;
if (currentLength + currentRun.Text.Length == length)
if (currentLength + currentRun.TextSourceLength == length)
{
var second = secondCount > 0 ? new List<DrawableTextRun>(secondCount) : null;
if (second != null)
{
var offset = currentRun.Text.Length >= 1 ? 1 : 0;
var offset = currentRun.TextSourceLength >= 1 ? 1 : 0;
for (var j = 0; j < secondCount; j++)
{
@ -124,16 +124,14 @@ namespace Avalonia.Media.TextFormatting
var second = new List<DrawableTextRun>(secondCount);
if (currentRun is not ShapedTextCharacters shapedTextCharacters)
if (currentRun is ShapedTextCharacters shapedTextCharacters)
{
throw new NotSupportedException("Only shaped runs can be split in between.");
}
var split = shapedTextCharacters.Split(length - currentLength);
var split = shapedTextCharacters.Split(length - currentLength);
first.Add(split.First);
first.Add(split.First);
second.Add(split.Second!);
second.Add(split.Second!);
}
for (var j = 1; j < secondCount; j++)
{
@ -483,7 +481,7 @@ namespace Avalonia.Media.TextFormatting
{
case ShapedTextCharacters shapedTextCharacters:
{
var firstCluster = shapedTextCharacters.Text.Start;
var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphClusters[0];
var lastCluster = firstCluster;
for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++)
@ -492,7 +490,7 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth)
{
measuredLength += Math.Max(0, lastCluster - firstCluster + 1);
measuredLength += Math.Max(0, lastCluster - firstCluster);
goto found;
}

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

@ -404,7 +404,7 @@ namespace Avalonia.Media.TextFormatting
var result = new List<TextBounds>(TextRuns.Count);
var lastDirection = _flowDirection;
var currentDirection = lastDirection;
var currentPosition = 0;
var currentPosition = FirstTextSourceIndex;
var currentRect = Rect.Empty;
var startX = Start;
@ -418,6 +418,11 @@ namespace Avalonia.Media.TextFormatting
continue;
}
if(currentPosition + currentRun.TextSourceLength <= firstTextSourceCharacterIndex)
{
continue;
}
TextRun? nextRun = null;
if (index + 1 < TextRuns.Count)
@ -1018,31 +1023,21 @@ namespace Avalonia.Media.TextFormatting
private TextLineMetrics CreateLineMetrics()
{
var start = 0d;
var height = 0d;
var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface;
var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight;
var width = 0d;
var widthIncludingWhitespace = 0d;
var trailingWhitespaceLength = 0;
var newLineLength = 0;
var ascent = 0d;
var descent = 0d;
var lineGap = 0d;
var fontRenderingEmSize = 0d;
var ascent = glyphTypeface.Ascent * scale;
var descent = glyphTypeface.Descent * scale;
var lineGap = glyphTypeface.LineGap * scale;
var lineHeight = _paragraphProperties.LineHeight;
if (_textRuns.Count == 0)
{
var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface;
fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight;
ascent = glyphTypeface.Ascent * scale;
height = double.IsNaN(lineHeight) || MathUtilities.IsZero(lineHeight) ?
descent - ascent + lineGap :
lineHeight;
return new TextLineMetrics(false, height, 0, start, -ascent, 0, 0, 0);
}
var height = descent - ascent + lineGap;
var lineHeight = _paragraphProperties.LineHeight;
for (var index = 0; index < _textRuns.Count; index++)
{
@ -1166,12 +1161,15 @@ namespace Avalonia.Media.TextFormatting
}
}
start = GetParagraphOffsetX(width, widthIncludingWhitespace, _paragraphWidth,
var start = GetParagraphOffsetX(width, widthIncludingWhitespace, _paragraphWidth,
_paragraphProperties.TextAlignment, _paragraphProperties.FlowDirection);
if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight))
{
height = lineHeight;
if(lineHeight > height)
{
height = lineHeight;
}
}
return new TextLineMetrics(widthIncludingWhitespace > _paragraphWidth, height, newLineLength, start,

3
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -1,6 +1,5 @@
using System;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
@ -59,7 +58,7 @@ namespace Avalonia.Skia
var glyphIndex = (ushort)sourceInfo.Codepoint;
var glyphCluster = (int)sourceInfo.Cluster;
var glyphCluster = (int)(sourceInfo.Cluster);
var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale);

21
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -665,17 +665,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var text = "0123".AsMemory();
var shaperOption = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 0, CultureInfo.CurrentCulture);
var shapedBuffer = TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text), shaperOption);
var firstRun = new ShapedTextCharacters(shapedBuffer, defaultProperties);
var firstRun = new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, 1, text.Length), shaperOption), defaultProperties);
var textRuns = new List<TextRun>
{
new CustomDrawableRun(),
firstRun,
new CustomDrawableRun(),
new ShapedTextCharacters(shapedBuffer, defaultProperties),
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length + 2, text.Length), shaperOption), defaultProperties),
new CustomDrawableRun(),
new ShapedTextCharacters(shapedBuffer, defaultProperties)
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length * 2 + 3, text.Length), shaperOption), defaultProperties)
};
var textSource = new FixedRunsTextSource(textRuns);
@ -691,15 +690,25 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(1, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, firstRun.Text.Length);
textBounds = textLine.GetTextBounds(0, 1);
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width);
Assert.Equal(14, textBounds[0].Rectangle.Width);
textBounds = textLine.GetTextBounds(0, firstRun.Text.Length + 1);
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width);
textBounds = textLine.GetTextBounds(1, firstRun.Text.Length);
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width);
textBounds = textLine.GetTextBounds(1, firstRun.Text.Length + 1);
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width);
}
}

Loading…
Cancel
Save