From 17b9f246f3a2ed510a560584b589e2a170dadce2 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 6 May 2022 21:01:07 +0200 Subject: [PATCH] Text hit testing fixes --- .../Media/TextFormatting/TextLineImpl.cs | 15 ++++-- .../Media/TextFormatting/TextShaperOptions.cs | 4 +- .../Presenters/TextPresenter.cs | 5 -- src/Avalonia.Controls/TextBlock.cs | 6 ++- .../HeadlessPlatformStubs.cs | 2 +- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 2 +- .../Media/TextShaperImpl.cs | 2 +- .../Media/TextFormatting/TextLineTests.cs | 50 ++++++++++++------- .../HarfBuzzTextShaperImpl.cs | 2 +- .../Avalonia.UnitTests/MockTextShaperImpl.cs | 2 +- 10 files changed, 55 insertions(+), 35 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 73ec055bbe..26e73cdf3b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/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; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs index a7fe92dc9a..4e75bb921e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs +++ b/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 /// /// Get the bidi level of the text. /// - public sbyte BidLevel { get; } + public sbyte BidiLevel { get; } /// /// Get the culture. diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 0785149a73..62ea05c68c 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/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; diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index bbe6aeb7ee..1a69d1218c 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/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; diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index 083b16c107..22ff8e8f97 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/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); } diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index 908b0ffa47..777e907617 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/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()) diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs index f4e4b00147..6e32d32913 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs +++ b/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()) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index a47638d2ec..f29dddf86b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/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 - { - new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice(text), ltrOptions), defaultProperties), - new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice(text, text.Length, text.Length), ltrOptions), defaultProperties), - new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice(text, text.Length * 2, text.Length), rtlOptions), defaultProperties), - new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice(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); } } diff --git a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs index 5f8854b3ab..4bc30484e9 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs +++ b/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()) diff --git a/tests/Avalonia.UnitTests/MockTextShaperImpl.cs b/tests/Avalonia.UnitTests/MockTextShaperImpl.cs index c4b1e6c154..7c34bd192e 100644 --- a/tests/Avalonia.UnitTests/MockTextShaperImpl.cs +++ b/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);