From a2a0fba1c2134b552080c83b0fdcc9f4872847e5 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 1 Sep 2020 07:44:38 +0200 Subject: [PATCH] Ignore invisible characters --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 5 ++++ .../Media/TextFormatting/TextLayout.cs | 6 ++--- .../Media/TextFormatting/TextLine.cs | 4 +-- .../Media/TextFormatting/TextLineImpl.cs | 17 +++++++++--- .../Media/TextFormatting/TextLineTests.cs | 26 +++++++++++++++++++ 5 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 src/Avalonia.Visuals/ApiCompatBaseline.txt diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt new file mode 100644 index 0000000000..5058cff26d --- /dev/null +++ b/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 diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index fa7d6cb4bf..df1ecb4067 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/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; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs index 423ca9fb7f..c052fb8948 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs +++ b/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. /// /// - /// A value that represents the line break. + /// A value that represents the line break. /// - public abstract TextLineBreak LineBreak { get; } + public abstract TextLineBreak TextLineBreak { get; } /// /// Gets a value that indicates whether the line is collapsed. diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 08d9107bb1..51092cddda 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/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; } /// - public override TextLineBreak LineBreak { get; } + public override TextLineBreak TextLineBreak { get; } /// 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); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 7abfe29f11..8f1bd5979a 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/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