Browse Source

Text hit testing fixes

pull/8094/head
Benedikt Stebner 4 years ago
parent
commit
17b9f246f3
  1. 15
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  2. 4
      src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
  3. 5
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  4. 6
      src/Avalonia.Controls/TextBlock.cs
  5. 2
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  6. 2
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  7. 2
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
  8. 50
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
  9. 2
      tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
  10. 2
      tests/Avalonia.UnitTests/MockTextShaperImpl.cs

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

@ -407,6 +407,7 @@ namespace Avalonia.Media.TextFormatting
var currentPosition = FirstTextSourceIndex;
var currentRect = Rect.Empty;
var startX = Start;
var runStart = startX;
//A portion of the line is covered.
for (var index = 0; index < TextRuns.Count; index++)
@ -431,7 +432,7 @@ namespace Avalonia.Media.TextFormatting
{
case ShapedTextCharacters when currentRun is ShapedTextCharacters:
{
if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End)
if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End && _flowDirection == FlowDirection.LeftToRight)
{
goto skip;
}
@ -480,7 +481,7 @@ namespace Avalonia.Media.TextFormatting
case ShapedTextCharacters shapedRun:
{
endOffset = shapedRun.GlyphRun.GetDistanceFromCharacterHit(
shapedRun.ShapedBuffer.IsLeftToRight ?
shapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(firstTextSourceCharacterIndex + textLength) :
new CharacterHit(firstTextSourceCharacterIndex));
@ -493,7 +494,7 @@ namespace Avalonia.Media.TextFormatting
startX += startOffset;
var characterHit = shapedRun.GlyphRun.IsLeftToRight ?
var characterHit = _flowDirection == FlowDirection.LeftToRight ?
shapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _) :
shapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
@ -580,6 +581,11 @@ namespace Avalonia.Media.TextFormatting
{
break;
}
if (_flowDirection == FlowDirection.RightToLeft)
{
endX += currentRun.Size.Width - endOffset;
}
}
else
{
@ -591,8 +597,9 @@ namespace Avalonia.Media.TextFormatting
endX += currentRun.Size.Width - endOffset;
}
lastDirection = currentDirection;
startX = endX;
lastDirection = currentDirection;
runStart += currentRun.Size.Width;
}
return result;

4
src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs

@ -16,7 +16,7 @@ namespace Avalonia.Media.TextFormatting
{
Typeface = typeface;
FontRenderingEmSize = fontRenderingEmSize;
BidLevel = bidiLevel;
BidiLevel = bidiLevel;
Culture = culture;
IncrementalTabWidth = incrementalTabWidth;
}
@ -33,7 +33,7 @@ namespace Avalonia.Media.TextFormatting
/// <summary>
/// Get the bidi level of the text.
/// </summary>
public sbyte BidLevel { get; }
public sbyte BidiLevel { get; }
/// <summary>
/// Get the culture.

5
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -515,11 +515,6 @@ namespace Avalonia.Controls.Presenters
protected override Size MeasureOverride(Size availableSize)
{
if (string.IsNullOrEmpty(Text))
{
return new Size();
}
_constraint = availableSize;
_textLayout = null;

6
src/Avalonia.Controls/TextBlock.cs

@ -631,7 +631,11 @@ namespace Avalonia.Controls
return finalSize;
}
_constraint = new Size(finalSize.Width, double.PositiveInfinity);
var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
_constraint = new Size(finalSize.Deflate(padding).Width, double.PositiveInfinity);
_textLayout = null;

2
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -137,7 +137,7 @@ namespace Avalonia.Headless
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
var bidiLevel = options.BidiLevel;
return new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel);
}

2
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -16,7 +16,7 @@ namespace Avalonia.Skia
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
var bidiLevel = options.BidiLevel;
var culture = options.Culture;
using (var buffer = new Buffer())

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

@ -16,7 +16,7 @@ namespace Avalonia.Direct2D1.Media
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
var bidiLevel = options.BidiLevel;
var culture = options.Culture;
using (var buffer = new Buffer())

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

@ -718,31 +718,45 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var text = "0123".AsMemory();
var ltrOptions = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 0, CultureInfo.CurrentCulture);
var rtlOptions = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 1, CultureInfo.CurrentCulture);
var textRuns = new List<TextRun>
{
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text), ltrOptions), defaultProperties),
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length, text.Length), ltrOptions), defaultProperties),
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length * 2, text.Length), rtlOptions), defaultProperties),
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length * 3, text.Length), ltrOptions), defaultProperties)
};
var textSource = new FixedRunsTextSource(textRuns);
var text = "אאא AAA";
var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
formatter.FormatLine(textSource, 0, 200,
new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0));
var textBounds = textLine.GetTextBounds(0, text.Length * 4);
var textBounds = textLine.GetTextBounds(0, text.Length);
Assert.Equal(3, textBounds.Count);
Assert.Equal(2, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 4);
var secondRun = textLine.TextRuns[1] as ShapedTextCharacters;
Assert.Equal(1, textBounds.Count);
Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(4, 3);
var firstRun = textLine.TextRuns[0] as ShapedTextCharacters;
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 5);
Assert.Equal(2, textBounds.Count);
Assert.Equal(7.201171875, textBounds[0].Rectangle.Width);
Assert.Equal(textLine.Start, textBounds[0].Rectangle.Left);
Assert.Equal(secondRun.Size.Width, textBounds[1].Rectangle.Width);
Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left);
}
}

2
tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs

@ -15,7 +15,7 @@ namespace Avalonia.UnitTests
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
var bidiLevel = options.BidiLevel;
var culture = options.Culture;
using (var buffer = new Buffer())

2
tests/Avalonia.UnitTests/MockTextShaperImpl.cs

@ -11,7 +11,7 @@ namespace Avalonia.UnitTests
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
var bidiLevel = options.BidiLevel;
var shapedBuffer = new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel);

Loading…
Cancel
Save