Browse Source

Merge pull request #4591 from Gillibald/fixes/IgnoreInvisibleCharacters

Ignore invisible characters for TextLine caret navigation
danwalmsley 5 years ago
parent
commit
cbd5a4c59a

+ 5 - 0
src/Avalonia.Visuals/ApiCompatBaseline.txt

@@ -0,0 +1,5 @@
+Compat issues with assembly Avalonia.Visuals:
+CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract.
+MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract.
+CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract.
+Total Issues: 3

+ 3 - 3
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@@ -221,7 +221,7 @@ namespace Avalonia.Media.TextFormatting
                 while (currentPosition < _text.Length)
                 {
                     var textLine = TextFormatter.Current.FormatLine(textSource, currentPosition, MaxWidth,
-                        _paragraphProperties, previousLine?.LineBreak);
+                        _paragraphProperties, previousLine?.TextLineBreak);
 
                     currentPosition += textLine.TextRange.Length;
 
@@ -230,7 +230,7 @@ namespace Avalonia.Media.TextFormatting
                         if (textLines.Count == MaxLines || !double.IsPositiveInfinity(MaxHeight) &&
                             height + textLine.LineMetrics.Size.Height > MaxHeight)
                         {
-                            if (previousLine?.LineBreak != null && _textTrimming != TextTrimming.None)
+                            if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None)
                             {
                                 var collapsedLine =
                                     previousLine.Collapse(GetCollapsingProperties(MaxWidth));
@@ -255,7 +255,7 @@ namespace Avalonia.Media.TextFormatting
 
                     previousLine = textLine;
 
-                    if (currentPosition != _text.Length || textLine.LineBreak == null)
+                    if (currentPosition != _text.Length || textLine.TextLineBreak == null)
                     {
                         continue;
                     }

+ 2 - 2
src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs

@@ -35,9 +35,9 @@ namespace Avalonia.Media.TextFormatting
         /// Gets the state of the line when broken by line breaking process.
         /// </summary>
         /// <returns>
-        /// A <see cref="LineBreak"/> value that represents the line break.
+        /// A <see cref="TextLineBreak"/> value that represents the line break.
         /// </returns>
-        public abstract TextLineBreak LineBreak { get; }
+        public abstract TextLineBreak TextLineBreak { get; }
 
         /// <summary>
         /// Gets a value that indicates whether the line is collapsed.

+ 14 - 3
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Media.TextFormatting
         {
             _textRuns = textRuns;
             LineMetrics = lineMetrics;
-            LineBreak = lineBreak;
+            TextLineBreak = lineBreak;
             HasCollapsed = hasCollapsed;
         }
 
@@ -27,7 +27,7 @@ namespace Avalonia.Media.TextFormatting
         public override TextLineMetrics LineMetrics { get; }
 
         /// <inheritdoc/>
-        public override TextLineBreak LineBreak { get; }
+        public override TextLineBreak TextLineBreak { get; }
 
         /// <inheritdoc/>
         public override bool HasCollapsed { get; }
@@ -122,7 +122,7 @@ namespace Avalonia.Media.TextFormatting
                     textLineMetrics = new TextLineMetrics(new Size(shapedWidth, LineMetrics.Size.Height),
                         LineMetrics.TextBaseline, textRange, false);
 
-                    return new TextLineImpl(shapedTextCharacters, textLineMetrics, LineBreak, true);
+                    return new TextLineImpl(shapedTextCharacters, textLineMetrics, TextLineBreak, true);
                 }
 
                 availableWidth -= currentRun.GlyphRun.Bounds.Width;
@@ -268,6 +268,17 @@ namespace Avalonia.Media.TextFormatting
                 var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength ==
                               TextRange.Length;
 
+                var characterIndex = codepointIndex - run.Text.Start;
+
+                var codepoint = Codepoint.ReadAt(run.GlyphRun.Characters, characterIndex, out _);
+
+                if (codepoint.IsBreakChar)
+                {
+                    foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(codepointIndex - 1, out _);
+
+                    isAtEnd = true;
+                }
+
                 nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ?
                     foundCharacterHit :
                     new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);

+ 26 - 0
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@@ -1,4 +1,6 @@
 using System;
+using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using Avalonia.Media;
 using Avalonia.Media.TextFormatting;
@@ -331,6 +333,30 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
             }
         }
 
+        [Fact]
+        public void Should_Ignore_Invisible_Characters()
+        {
+            using (Start())
+            {
+                var defaultTextRunProperties =
+                    new GenericTextRunProperties(Typeface.Default);
+
+                const string text = "01234567🎉\n";
+
+                var source = new SingleBufferTextSource(text, defaultTextRunProperties);
+
+                var textParagraphProperties = new GenericTextParagraphProperties(defaultTextRunProperties);
+
+                var formatter = TextFormatter.Current;
+
+                var textLine = formatter.FormatLine(source, 0, double.PositiveInfinity, textParagraphProperties);
+
+                var nextCharacterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(8, 2));
+
+                Assert.Equal(new CharacterHit(8, 2), nextCharacterHit);
+            }
+        }
+
         private static IDisposable Start()
         {
             var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface