Browse Source

Merge pull request #10784 from Gillibald/fixes/moreTextProcessingFixes

[Text] Fixes
gh-readonly-queue/master/pr-10775-5177dcdb1a9d225bf196f6567dc4b7cf8df3a857
Max Katz 3 years ago
committed by GitHub
parent
commit
5177dcdb1a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  2. 6
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  3. 13
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
  4. 26
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  5. 24
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
  6. 6
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs
  7. 12
      tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs

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

@ -697,13 +697,18 @@ namespace Avalonia.Media.TextFormatting
i = lastRunIndex;
//Possible overlap at runs of different direction
if (directionalWidth == 0)
{
continue;
//In case a run only contains a linebreak we don't want to skip it.
if (currentRun is ShapedTextRun shaped && currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
{
continue;
}
}
var coveredLength = 0;
TextBounds? textBounds = null;
int coveredLength;
TextBounds? textBounds;
switch (currentDirection)
{
@ -831,14 +836,18 @@ namespace Avalonia.Media.TextFormatting
i = firstRunIndex;
//Possible overlap at runs of different direction
if (directionalWidth == 0)
{
continue;
//In case a run only contains a linebreak we don't want to skip it.
if (currentRun is ShapedTextRun shaped && currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
{
continue;
}
}
var coveredLength = 0;
TextBounds? textBounds = null;
int coveredLength;
switch (currentDirection)
{

6
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -26,8 +26,8 @@ namespace Avalonia.Skia
using (var buffer = new Buffer())
{
// HarfBuzz needs the surrounding characters to correctly shape the text
var containingText = GetContainingMemory(text, out var start, out var length);
buffer.AddUtf16(containingText.Span, start, length);
var containingText = GetContainingMemory(text, out var start, out var length).Span;
buffer.AddUtf16(containingText, start, length);
MergeBreakPair(buffer);
@ -72,7 +72,7 @@ namespace Avalonia.Skia
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
if (i < textSpan.Length && textSpan[i] == '\t')
if (glyphCluster < containingText.Length && containingText[glyphCluster] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');

13
src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs

@ -1,5 +1,6 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Globalization;
using System.Runtime.InteropServices;
using Avalonia.Media.TextFormatting;
@ -13,6 +14,8 @@ namespace Avalonia.Direct2D1.Media
{
internal class TextShaperImpl : ITextShaperImpl
{
private static readonly ConcurrentDictionary<int, Language> s_cachedLanguage = new();
public ShapedBuffer ShapeText(ReadOnlyMemory<char> text, TextShaperOptions options)
{
var textSpan = text.Span;
@ -24,8 +27,8 @@ namespace Avalonia.Direct2D1.Media
using (var buffer = new Buffer())
{
// HarfBuzz needs the surrounding characters to correctly shape the text
var containingText = GetContainingMemory(text, out var start, out var length);
buffer.AddUtf16(containingText.Span, start, length);
var containingText = GetContainingMemory(text, out var start, out var length).Span;
buffer.AddUtf16(containingText, start, length);
MergeBreakPair(buffer);
@ -33,7 +36,9 @@ namespace Avalonia.Direct2D1.Media
buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft;
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var usedCulture = culture ?? CultureInfo.CurrentCulture;
buffer.Language = s_cachedLanguage.GetOrAdd(usedCulture.LCID, _ => new Language(usedCulture));
var font = ((GlyphTypefaceImpl)typeface).Font;
@ -68,7 +73,7 @@ namespace Avalonia.Direct2D1.Media
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
if (i < textSpan.Length && textSpan[i] == '\t')
if (glyphCluster < containingText.Length && containingText[glyphCluster] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');

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

@ -1051,7 +1051,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
[InlineData("שנב🧐שנב", 2, 4, FlowDirection.LeftToRight, "11.268,38.208")]
[InlineData("שנב🧐שנב", 2, 4, FlowDirection.RightToLeft, "11.268,38.208")]
[Theory]
public void Should_HitTextTextRangeBetweenRuns(string text, int start, int length,
public void Should_HitTestTextRangeBetweenRuns(string text, int start, int length,
FlowDirection flowDirection, string expected)
{
using (Start())
@ -1087,6 +1087,30 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_HitTestTextRangeWithLineBreaks()
{
using (Start())
{
var beforeLinebreak = "Line before linebreak";
var afterLinebreak = "Line after linebreak";
var text = beforeLinebreak + Environment.NewLine + "" + Environment.NewLine + afterLinebreak;
var textLayout = new TextLayout(text, Typeface.Default, 12, Brushes.Black);
var end = text.Length - afterLinebreak.Length + 1;
var rects = textLayout.HitTestTextRange(0, end).ToArray();
Assert.Equal(3, rects.Length);
var endX = textLayout.TextLines[2].GetDistanceFromCharacterHit(new CharacterHit(end));
//First character should be covered
Assert.Equal(7.201171875, endX, 2);
}
}
private static IDisposable Start()
{

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

@ -622,6 +622,30 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_Get_TextBounds_For_LineBreak()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new SingleBufferTextSource(Environment.NewLine, defaultProperties);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var textBounds = textLine.GetTextBounds(0, Environment.NewLine.Length);
Assert.Equal(1, textBounds.Count);
Assert.Equal(1, textBounds[0].TextRunBounds.Count);
Assert.Equal(Environment.NewLine.Length, textBounds[0].TextRunBounds[0].Length);
}
}
[Fact]
public void Should_GetTextRange()
{

6
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs

@ -31,11 +31,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
using (Start())
{
var text = "\t";
var text = "012345\t";
var options = new TextShaperOptions(Typeface.Default.GlyphTypeface, 12, 0, CultureInfo.CurrentCulture, 100);
var shapedBuffer = TextShaper.Current.ShapeText(text, options);
var shapedBuffer = TextShaper.Current.ShapeText(text.AsMemory().Slice(6), options);
Assert.Equal(shapedBuffer.Length, text.Length);
Assert.Equal(1, shapedBuffer.Length);
Assert.Equal(100, shapedBuffer[0].GlyphAdvance);
}
}

12
tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs

@ -1,5 +1,6 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Globalization;
using System.Runtime.InteropServices;
using Avalonia.Media.TextFormatting;
@ -13,6 +14,7 @@ namespace Avalonia.UnitTests
{
internal class HarfBuzzTextShaperImpl : ITextShaperImpl
{
private static readonly ConcurrentDictionary<int, Language> s_cachedLanguage = new();
public ShapedBuffer ShapeText(ReadOnlyMemory<char> text, TextShaperOptions options)
{
var textSpan = text.Span;
@ -24,8 +26,8 @@ namespace Avalonia.UnitTests
using (var buffer = new Buffer())
{
// HarfBuzz needs the surrounding characters to correctly shape the text
var containingText = GetContainingMemory(text, out var start, out var length);
buffer.AddUtf16(containingText.Span, start, length);
var containingText = GetContainingMemory(text, out var start, out var length).Span;
buffer.AddUtf16(containingText, start, length);
MergeBreakPair(buffer);
@ -33,7 +35,9 @@ namespace Avalonia.UnitTests
buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft;
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var usedCulture = culture ?? CultureInfo.CurrentCulture;
buffer.Language = s_cachedLanguage.GetOrAdd(usedCulture.LCID, _ => new Language(usedCulture));
var font = ((HarfBuzzGlyphTypefaceImpl)typeface).Font;
@ -68,7 +72,7 @@ namespace Avalonia.UnitTests
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
if (textSpan[i] == '\t')
if (glyphCluster < containingText.Length && containingText[glyphCluster] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');

Loading…
Cancel
Save