Browse Source

Some more hit testing fixes

pull/7946/head
Benedikt Stebner 5 years ago
parent
commit
3e6bc0b48d
  1. 44
      src/Avalonia.Base/Media/GlyphRun.cs
  2. 2
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  3. 74
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  4. 97
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

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

@ -49,7 +49,7 @@ namespace Avalonia.Media
IReadOnlyList<int>? glyphClusters = null, IReadOnlyList<int>? glyphClusters = null,
int biDiLevel = 0) int biDiLevel = 0)
{ {
_glyphTypeface = glyphTypeface; _glyphTypeface = glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize; FontRenderingEmSize = fontRenderingEmSize;
@ -204,7 +204,7 @@ namespace Avalonia.Media
public double GetDistanceFromCharacterHit(CharacterHit characterHit) public double GetDistanceFromCharacterHit(CharacterHit characterHit)
{ {
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var distance = 0.0; var distance = 0.0;
if (IsLeftToRight) if (IsLeftToRight)
@ -223,7 +223,7 @@ namespace Avalonia.Media
} }
var glyphIndex = FindGlyphIndex(characterIndex); var glyphIndex = FindGlyphIndex(characterIndex);
if (GlyphClusters != null) if (GlyphClusters != null)
{ {
var currentCluster = GlyphClusters[glyphIndex]; var currentCluster = GlyphClusters[glyphIndex];
@ -249,7 +249,7 @@ namespace Avalonia.Media
{ {
//RightToLeft //RightToLeft
var glyphIndex = FindGlyphIndex(characterIndex); var glyphIndex = FindGlyphIndex(characterIndex);
if (GlyphClusters != null) if (GlyphClusters != null)
{ {
if (characterIndex > GlyphClusters[0]) if (characterIndex > GlyphClusters[0])
@ -284,13 +284,13 @@ namespace Avalonia.Media
public CharacterHit GetCharacterHitFromDistance(double distance, out bool isInside) public CharacterHit GetCharacterHitFromDistance(double distance, out bool isInside)
{ {
var characterIndex = 0; var characterIndex = 0;
// Before // Before
if (distance <= 0) if (distance <= 0)
{ {
isInside = false; isInside = false;
if(GlyphClusters != null) if (GlyphClusters != null)
{ {
characterIndex = GlyphClusters[characterIndex]; characterIndex = GlyphClusters[characterIndex];
} }
@ -307,11 +307,11 @@ namespace Avalonia.Media
characterIndex = GlyphIndices.Count - 1; characterIndex = GlyphIndices.Count - 1;
if(GlyphClusters != null) if (GlyphClusters != null)
{ {
characterIndex = GlyphClusters[characterIndex]; characterIndex = GlyphClusters[characterIndex];
} }
var lastCharacterHit = FindNearestCharacterHit(characterIndex, out _); var lastCharacterHit = FindNearestCharacterHit(characterIndex, out _);
return IsLeftToRight ? lastCharacterHit : new CharacterHit(lastCharacterHit.FirstCharacterIndex); return IsLeftToRight ? lastCharacterHit : new CharacterHit(lastCharacterHit.FirstCharacterIndex);
@ -327,7 +327,7 @@ namespace Avalonia.Media
var advance = GetGlyphAdvance(index, out var cluster); var advance = GetGlyphAdvance(index, out var cluster);
characterIndex = cluster; characterIndex = cluster;
if (distance > currentX && distance <= currentX + advance) if (distance > currentX && distance <= currentX + advance)
{ {
break; break;
@ -345,7 +345,7 @@ namespace Avalonia.Media
var advance = GetGlyphAdvance(index, out var cluster); var advance = GetGlyphAdvance(index, out var cluster);
characterIndex = cluster; characterIndex = cluster;
if (currentX - advance < distance) if (currentX - advance < distance)
{ {
break; break;
@ -554,6 +554,16 @@ namespace Avalonia.Media
nextCluster = GlyphClusters[currentIndex]; nextCluster = GlyphClusters[currentIndex];
} }
if (nextCluster < Characters.Start)
{
nextCluster = Characters.Start;
}
if (cluster < Characters.Start)
{
cluster = Characters.Start;
}
int trailingLength; int trailingLength;
if (nextCluster == cluster) if (nextCluster == cluster)
@ -577,7 +587,7 @@ namespace Avalonia.Media
private double GetGlyphAdvance(int index, out int cluster) private double GetGlyphAdvance(int index, out int cluster)
{ {
cluster = GlyphClusters != null ? GlyphClusters[index] : index; cluster = GlyphClusters != null ? GlyphClusters[index] : index;
if (GlyphAdvances != null) if (GlyphAdvances != null)
{ {
return GlyphAdvances[index]; return GlyphAdvances[index];
@ -603,7 +613,7 @@ namespace Avalonia.Media
var widthIncludingTrailingWhitespace = 0d; var widthIncludingTrailingWhitespace = 0d;
var trailingWhitespaceLength = GetTrailingWhitespaceLength(out var newLineLength, out var glyphCount); var trailingWhitespaceLength = GetTrailingWhitespaceLength(out var newLineLength, out var glyphCount);
for (var index = 0; index < GlyphIndices.Count; index++) for (var index = 0; index < GlyphIndices.Count; index++)
{ {
var advance = GetGlyphAdvance(index, out _); var advance = GetGlyphAdvance(index, out _);
@ -615,7 +625,7 @@ namespace Avalonia.Media
if (IsLeftToRight) if (IsLeftToRight)
{ {
for (var index = GlyphIndices.Count - glyphCount; index <GlyphIndices.Count; index++) for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++)
{ {
width -= GetGlyphAdvance(index, out _); width -= GetGlyphAdvance(index, out _);
} }
@ -677,12 +687,12 @@ namespace Avalonia.Media
if (codepointIndex < 0) if (codepointIndex < 0)
{ {
trailingWhitespaceLength = _characters.Length; trailingWhitespaceLength = _characters.Length;
glyphCount = GlyphClusters.Count; glyphCount = GlyphClusters.Count;
break; break;
} }
var codepoint = Codepoint.ReadAt(_characters, codepointIndex, out _); var codepoint = Codepoint.ReadAt(_characters, codepointIndex, out _);
if (!codepoint.IsWhiteSpace) if (!codepoint.IsWhiteSpace)
@ -696,7 +706,7 @@ namespace Avalonia.Media
} }
trailingWhitespaceLength++; trailingWhitespaceLength++;
glyphCount++; glyphCount++;
} }
} }

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

@ -544,7 +544,7 @@ namespace Avalonia.Media.TextFormatting
var textRuns = new List<DrawableTextRun> { new ShapedTextCharacters(shapedBuffer, properties) }; var textRuns = new List<DrawableTextRun> { new ShapedTextCharacters(shapedBuffer, properties) };
return new TextLineImpl(textRuns, firstTextSourceIndex, 1, double.PositiveInfinity, paragraphProperties, flowDirection).FinalizeLine(); return new TextLineImpl(textRuns, firstTextSourceIndex, 0, double.PositiveInfinity, paragraphProperties, flowDirection).FinalizeLine();
} }
/// <summary> /// <summary>

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

@ -183,6 +183,7 @@ namespace Avalonia.Media.TextFormatting
case ShapedTextCharacters shapedRun: case ShapedTextCharacters shapedRun:
{ {
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
break; break;
} }
default: default:
@ -426,31 +427,42 @@ namespace Avalonia.Media.TextFormatting
if (nextRun != null) if (nextRun != null)
{ {
if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End) switch (nextRun)
{ {
goto skip; case ShapedTextCharacters when currentRun is ShapedTextCharacters:
} {
if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End)
{
goto skip;
}
if (currentRun.Text.Start >= firstTextSourceCharacterIndex + textLength) if (currentRun.Text.Start >= firstTextSourceCharacterIndex + textLength)
{ {
goto skip; goto skip;
} }
if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < firstTextSourceCharacterIndex) if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < firstTextSourceCharacterIndex)
{ {
goto skip; goto skip;
} }
if (currentRun.Text.End < firstTextSourceCharacterIndex) if (currentRun.Text.End < firstTextSourceCharacterIndex)
{ {
goto skip; goto skip;
} }
goto noop; goto noop;
}
default:
{
goto noop;
}
}
skip: skip:
{ {
startX += currentRun.Size.Width; startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
} }
continue; continue;
@ -460,7 +472,6 @@ namespace Avalonia.Media.TextFormatting
} }
} }
var endX = startX; var endX = startX;
var endOffset = 0d; var endOffset = 0d;
@ -520,11 +531,13 @@ namespace Avalonia.Media.TextFormatting
} }
default: default:
{ {
if (firstTextSourceCharacterIndex + textLength >= currentRun.Text.Start + currentRun.Text.Length) if (currentPosition + currentRun.TextSourceLength <= firstTextSourceCharacterIndex + textLength)
{ {
endX += currentRun.Size.Width; endX += currentRun.Size.Width;
} }
currentPosition += currentRun.TextSourceLength;
break; break;
} }
} }
@ -538,7 +551,9 @@ namespace Avalonia.Media.TextFormatting
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
{ {
var textBounds = new TextBounds(currentRect.WithWidth(currentRect.Width + width), currentDirection); currentRect = currentRect.WithWidth(currentRect.Width + width);
var textBounds = new TextBounds(currentRect, currentDirection);
result[result.Count - 1] = textBounds; result[result.Count - 1] = textBounds;
} }
@ -551,21 +566,9 @@ namespace Avalonia.Media.TextFormatting
if (currentDirection == FlowDirection.LeftToRight) if (currentDirection == FlowDirection.LeftToRight)
{ {
if (nextRun != null) if (currentPosition >= firstTextSourceCharacterIndex + textLength)
{
if (nextRun.Text.Start > currentRun.Text.Start && nextRun.Text.Start >= firstTextSourceCharacterIndex + textLength)
{
break;
}
currentPosition = nextRun.Text.End;
}
else
{ {
if (currentPosition >= firstTextSourceCharacterIndex + textLength) break;
{
break;
}
} }
} }
else else
@ -575,10 +578,7 @@ namespace Avalonia.Media.TextFormatting
break; break;
} }
if (currentPosition != currentRun.Text.Start) endX += currentRun.Size.Width - endOffset;
{
endX += currentRun.Size.Width - endOffset;
}
} }
lastDirection = currentDirection; lastDirection = currentDirection;

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

@ -70,12 +70,12 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
} }
} }
} }
[Fact] [Fact]
public void Should_Get_Next_Caret_CharacterHit_Bidi() public void Should_Get_Next_Caret_CharacterHit_Bidi()
{ {
const string text = "אבג 1 ABC"; const string text = "אבג 1 ABC";
using (Start()) using (Start())
{ {
var defaultProperties = new GenericTextRunProperties(Typeface.Default); var defaultProperties = new GenericTextRunProperties(Typeface.Default);
@ -90,7 +90,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var clusters = new List<int>(); var clusters = new List<int>();
foreach (var textRun in textLine.TextRuns.OrderBy(x=> x.Text.Start)) foreach (var textRun in textLine.TextRuns.OrderBy(x => x.Text.Start))
{ {
var shapedRun = (ShapedTextCharacters)textRun; var shapedRun = (ShapedTextCharacters)textRun;
@ -98,7 +98,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
shapedRun.ShapedBuffer.GlyphClusters.Reverse() : shapedRun.ShapedBuffer.GlyphClusters.Reverse() :
shapedRun.ShapedBuffer.GlyphClusters); shapedRun.ShapedBuffer.GlyphClusters);
} }
var nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]); var nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]);
foreach (var cluster in clusters) foreach (var cluster in clusters)
@ -122,7 +122,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
public void Should_Get_Previous_Caret_CharacterHit_Bidi() public void Should_Get_Previous_Caret_CharacterHit_Bidi()
{ {
const string text = "אבג 1 ABC"; const string text = "אבג 1 ABC";
using (Start()) using (Start())
{ {
var defaultProperties = new GenericTextRunProperties(Typeface.Default); var defaultProperties = new GenericTextRunProperties(Typeface.Default);
@ -137,7 +137,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var clusters = new List<int>(); var clusters = new List<int>();
foreach (var textRun in textLine.TextRuns.OrderBy(x=> x.Text.Start)) foreach (var textRun in textLine.TextRuns.OrderBy(x => x.Text.Start))
{ {
var shapedRun = (ShapedTextCharacters)textRun; var shapedRun = (ShapedTextCharacters)textRun;
@ -147,13 +147,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
} }
clusters.Reverse(); clusters.Reverse();
var nextCharacterHit = new CharacterHit(text.Length - 1); var nextCharacterHit = new CharacterHit(text.Length - 1);
foreach (var cluster in clusters) foreach (var cluster in clusters)
{ {
var currentCaretIndex = nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength; var currentCaretIndex = nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength;
Assert.Equal(cluster, currentCaretIndex); Assert.Equal(cluster, currentCaretIndex);
nextCharacterHit = textLine.GetPreviousCaretCharacterHit(nextCharacterHit); nextCharacterHit = textLine.GetPreviousCaretCharacterHit(nextCharacterHit);
@ -168,7 +168,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength); Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength);
} }
} }
[InlineData("𐐷𐐷𐐷𐐷𐐷")] [InlineData("𐐷𐐷𐐷𐐷𐐷")]
[InlineData("01234567🎉\n")] [InlineData("01234567🎉\n")]
[InlineData("𐐷1234")] [InlineData("𐐷1234")]
@ -324,7 +324,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
} }
} }
Assert.Equal(currentDistance,textLine.GetDistanceFromCharacterHit(new CharacterHit(s_multiLineText.Length))); Assert.Equal(currentDistance, textLine.GetDistanceFromCharacterHit(new CharacterHit(s_multiLineText.Length)));
} }
} }
@ -371,7 +371,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
yield return CreateData("01234 01234", 58, TextTrimming.WordEllipsis, "01234\u2026"); yield return CreateData("01234 01234", 58, TextTrimming.WordEllipsis, "01234\u2026");
yield return CreateData("01234", 9, TextTrimming.CharacterEllipsis, "\u2026"); yield return CreateData("01234", 9, TextTrimming.CharacterEllipsis, "\u2026");
yield return CreateData("01234", 2, TextTrimming.CharacterEllipsis, ""); yield return CreateData("01234", 2, TextTrimming.CharacterEllipsis, "");
object[] CreateData(string text, double width, TextTrimming mode, string expected) object[] CreateData(string text, double width, TextTrimming mode, string expected)
{ {
return new object[] return new object[]
@ -424,7 +424,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{ {
var defaultProperties = new GenericTextRunProperties(Typeface.Default); var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new DrawableRunTextSource(); var textSource = new DrawableRunTextSource();
var formatter = new TextFormatterImpl(); var formatter = new TextFormatterImpl();
var textLine = var textLine =
@ -471,7 +471,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(4, textLine.TextRuns.Count); Assert.Equal(4, textLine.TextRuns.Count);
var currentHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(3,1)); var currentHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(3, 1));
Assert.Equal(3, currentHit.FirstCharacterIndex); Assert.Equal(3, currentHit.FirstCharacterIndex);
Assert.Equal(0, currentHit.TrailingLength); Assert.Equal(0, currentHit.TrailingLength);
@ -552,11 +552,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
switch (textSourceIndex) switch (textSourceIndex)
{ {
case 0: case 0:
return new CustomDrawableRun(); return new CustomDrawableRun();
case 1: case 1:
return new TextCharacters(new ReadOnlySlice<char>(Text.AsMemory(), 1, 1, 1), new GenericTextRunProperties(Typeface.Default)); return new TextCharacters(new ReadOnlySlice<char>(Text.AsMemory(), 1, 1, 1), new GenericTextRunProperties(Typeface.Default));
case 2: case 2:
return new CustomDrawableRun(); return new CustomDrawableRun();
case 3: case 3:
return new TextCharacters(new ReadOnlySlice<char>(Text.AsMemory(), 3, 1, 3), new GenericTextRunProperties(Typeface.Default)); return new TextCharacters(new ReadOnlySlice<char>(Text.AsMemory(), 3, 1, 3), new GenericTextRunProperties(Typeface.Default));
default: default:
@ -564,14 +564,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
} }
} }
} }
private class CustomDrawableRun : DrawableTextRun private class CustomDrawableRun : DrawableTextRun
{ {
public override Size Size => new(14, 14); public override Size Size => new(14, 14);
public override double Baseline => 14; public override double Baseline => 14;
public override void Draw(DrawingContext drawingContext, Point origin) public override void Draw(DrawingContext drawingContext, Point origin)
{ {
} }
} }
@ -587,29 +587,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var shapedTextRuns = textLine.TextRuns.Cast<ShapedTextCharacters>().ToList(); var shapedTextRuns = textLine.TextRuns.Cast<ShapedTextCharacters>().ToList();
var lastCluster = -1; var lastCluster = -1;
foreach (var textRun in shapedTextRuns) foreach (var textRun in shapedTextRuns)
{ {
var shapedBuffer = textRun.ShapedBuffer; var shapedBuffer = textRun.ShapedBuffer;
var currentClusters = shapedBuffer.GlyphClusters.ToList(); var currentClusters = shapedBuffer.GlyphClusters.ToList();
foreach (var currentCluster in currentClusters) foreach (var currentCluster in currentClusters)
{ {
if (lastCluster == currentCluster) if (lastCluster == currentCluster)
{ {
continue; continue;
} }
glyphClusters.Add(currentCluster); glyphClusters.Add(currentCluster);
lastCluster = currentCluster; lastCluster = currentCluster;
} }
} }
return glyphClusters; return glyphClusters;
} }
private static List<Rect> BuildRects(TextLine textLine) private static List<Rect> BuildRects(TextLine textLine)
{ {
var rects = new List<Rect>(); var rects = new List<Rect>();
@ -624,11 +624,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
foreach (var textRun in shapedTextRuns) foreach (var textRun in shapedTextRuns)
{ {
var shapedBuffer = textRun.ShapedBuffer; var shapedBuffer = textRun.ShapedBuffer;
for (var index = 0; index < shapedBuffer.GlyphAdvances.Count; index++) for (var index = 0; index < shapedBuffer.GlyphAdvances.Count; index++)
{ {
var currentCluster = shapedBuffer.GlyphClusters[index]; var currentCluster = shapedBuffer.GlyphClusters[index];
var advance = shapedBuffer.GlyphAdvances[index]; var advance = shapedBuffer.GlyphAdvances[index];
if (lastCluster != currentCluster) if (lastCluster != currentCluster)
@ -642,10 +642,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
rects.Remove(rect); rects.Remove(rect);
rect = rect.WithWidth(rect.Width + advance); rect = rect.WithWidth(rect.Width + advance);
rects.Add(rect); rects.Add(rect);
} }
currentX += advance; currentX += advance;
lastCluster = currentCluster; lastCluster = currentCluster;
@ -655,8 +655,43 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
return rects; return rects;
} }
[Fact] [Fact]
public void Should_Get_TextBounds() public void Should_Get_TextBounds_Mixed()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var text = "0123".AsMemory();
var shaperOption = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 0, CultureInfo.CurrentCulture);
var textRuns = new List<TextRun>
{
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text), shaperOption), defaultProperties),
new CustomDrawableRun(),
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length + 1, text.Length), shaperOption), defaultProperties),
new CustomDrawableRun(),
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length * 2 + 2, text.Length), shaperOption), defaultProperties),
new CustomDrawableRun(),
};
var textSource = new FixedRunsTextSource(textRuns);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var textBounds = textLine.GetTextBounds(0, text.Length * 3 + 3);
Assert.Equal(1, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
}
}
[Fact]
public void Should_Get_TextBounds_BiDi()
{ {
using (Start()) using (Start())
{ {
@ -673,7 +708,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length * 3, text.Length), ltrOptions), defaultProperties) new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length * 3, text.Length), ltrOptions), defaultProperties)
}; };
var textSource = new FixedRunsTextSource(textRuns); var textSource = new FixedRunsTextSource(textRuns);
var formatter = new TextFormatterImpl(); var formatter = new TextFormatterImpl();
@ -700,12 +735,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
public TextRun? GetTextRun(int textSourceIndex) public TextRun? GetTextRun(int textSourceIndex)
{ {
var currentPosition = 0;
foreach (var textRun in _textRuns) foreach (var textRun in _textRuns)
{ {
if(textRun.Text.Start == textSourceIndex) if (currentPosition == textSourceIndex)
{ {
return textRun; return textRun;
} }
currentPosition += textRun.TextSourceLength;
} }
return null; return null;

Loading…
Cancel
Save