|
|
|
@ -70,12 +70,12 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void Should_Get_Next_Caret_CharacterHit_Bidi() |
|
|
|
{ |
|
|
|
const string text = "אבג 1 ABC"; |
|
|
|
|
|
|
|
|
|
|
|
using (Start()) |
|
|
|
{ |
|
|
|
var defaultProperties = new GenericTextRunProperties(Typeface.Default); |
|
|
|
@ -90,7 +90,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
@ -98,7 +98,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
shapedRun.ShapedBuffer.GlyphClusters.Reverse() : |
|
|
|
shapedRun.ShapedBuffer.GlyphClusters); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]); |
|
|
|
|
|
|
|
foreach (var cluster in clusters) |
|
|
|
@ -122,7 +122,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
public void Should_Get_Previous_Caret_CharacterHit_Bidi() |
|
|
|
{ |
|
|
|
const string text = "אבג 1 ABC"; |
|
|
|
|
|
|
|
|
|
|
|
using (Start()) |
|
|
|
{ |
|
|
|
var defaultProperties = new GenericTextRunProperties(Typeface.Default); |
|
|
|
@ -137,7 +137,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
@ -147,13 +147,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
} |
|
|
|
|
|
|
|
clusters.Reverse(); |
|
|
|
|
|
|
|
|
|
|
|
var nextCharacterHit = new CharacterHit(text.Length - 1); |
|
|
|
|
|
|
|
foreach (var cluster in clusters) |
|
|
|
{ |
|
|
|
var currentCaretIndex = nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength; |
|
|
|
|
|
|
|
|
|
|
|
Assert.Equal(cluster, currentCaretIndex); |
|
|
|
|
|
|
|
nextCharacterHit = textLine.GetPreviousCaretCharacterHit(nextCharacterHit); |
|
|
|
@ -168,7 +168,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
[InlineData("𐐷𐐷𐐷𐐷𐐷")] |
|
|
|
[InlineData("01234567🎉\n")] |
|
|
|
[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", 9, TextTrimming.CharacterEllipsis, "\u2026"); |
|
|
|
yield return CreateData("01234", 2, TextTrimming.CharacterEllipsis, ""); |
|
|
|
|
|
|
|
|
|
|
|
object[] CreateData(string text, double width, TextTrimming mode, string expected) |
|
|
|
{ |
|
|
|
return new object[] |
|
|
|
@ -424,7 +424,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
{ |
|
|
|
var defaultProperties = new GenericTextRunProperties(Typeface.Default); |
|
|
|
var textSource = new DrawableRunTextSource(); |
|
|
|
|
|
|
|
|
|
|
|
var formatter = new TextFormatterImpl(); |
|
|
|
|
|
|
|
var textLine = |
|
|
|
@ -471,7 +471,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
|
|
|
|
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(0, currentHit.TrailingLength); |
|
|
|
@ -552,11 +552,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
switch (textSourceIndex) |
|
|
|
{ |
|
|
|
case 0: |
|
|
|
return new CustomDrawableRun(); |
|
|
|
return new CustomDrawableRun(); |
|
|
|
case 1: |
|
|
|
return new TextCharacters(new ReadOnlySlice<char>(Text.AsMemory(), 1, 1, 1), new GenericTextRunProperties(Typeface.Default)); |
|
|
|
case 2: |
|
|
|
return new CustomDrawableRun(); |
|
|
|
return new CustomDrawableRun(); |
|
|
|
case 3: |
|
|
|
return new TextCharacters(new ReadOnlySlice<char>(Text.AsMemory(), 3, 1, 3), new GenericTextRunProperties(Typeface.Default)); |
|
|
|
default: |
|
|
|
@ -564,14 +564,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private class CustomDrawableRun : DrawableTextRun |
|
|
|
{ |
|
|
|
public override Size Size => new(14, 14); |
|
|
|
public override double Baseline => 14; |
|
|
|
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 lastCluster = -1; |
|
|
|
|
|
|
|
|
|
|
|
foreach (var textRun in shapedTextRuns) |
|
|
|
{ |
|
|
|
var shapedBuffer = textRun.ShapedBuffer; |
|
|
|
|
|
|
|
var currentClusters = shapedBuffer.GlyphClusters.ToList(); |
|
|
|
|
|
|
|
foreach (var currentCluster in currentClusters) |
|
|
|
foreach (var currentCluster in currentClusters) |
|
|
|
{ |
|
|
|
if (lastCluster == currentCluster) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
glyphClusters.Add(currentCluster); |
|
|
|
|
|
|
|
lastCluster = currentCluster; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return glyphClusters; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static List<Rect> BuildRects(TextLine textLine) |
|
|
|
{ |
|
|
|
var rects = new List<Rect>(); |
|
|
|
@ -624,11 +624,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
foreach (var textRun in shapedTextRuns) |
|
|
|
{ |
|
|
|
var shapedBuffer = textRun.ShapedBuffer; |
|
|
|
|
|
|
|
|
|
|
|
for (var index = 0; index < shapedBuffer.GlyphAdvances.Count; index++) |
|
|
|
{ |
|
|
|
var currentCluster = shapedBuffer.GlyphClusters[index]; |
|
|
|
|
|
|
|
|
|
|
|
var advance = shapedBuffer.GlyphAdvances[index]; |
|
|
|
|
|
|
|
if (lastCluster != currentCluster) |
|
|
|
@ -642,10 +642,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
rects.Remove(rect); |
|
|
|
|
|
|
|
rect = rect.WithWidth(rect.Width + advance); |
|
|
|
|
|
|
|
|
|
|
|
rects.Add(rect); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
currentX += advance; |
|
|
|
|
|
|
|
lastCluster = currentCluster; |
|
|
|
@ -655,8 +655,43 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
return rects; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
[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()) |
|
|
|
{ |
|
|
|
@ -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) |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var textSource = new FixedRunsTextSource(textRuns); |
|
|
|
|
|
|
|
var formatter = new TextFormatterImpl(); |
|
|
|
@ -700,12 +735,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
|
|
|
|
|
|
public TextRun? GetTextRun(int textSourceIndex) |
|
|
|
{ |
|
|
|
var currentPosition = 0; |
|
|
|
|
|
|
|
foreach (var textRun in _textRuns) |
|
|
|
{ |
|
|
|
if(textRun.Text.Start == textSourceIndex) |
|
|
|
if (currentPosition == textSourceIndex) |
|
|
|
{ |
|
|
|
return textRun; |
|
|
|
} |
|
|
|
|
|
|
|
currentPosition += textRun.TextSourceLength; |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|