Browse Source

Fixed the issue where a newline character required two backspaces to be deleted in Windows. (#20073)

* Fixed the issue where a newline character required two backspaces to be deleted in Windows.

* add unit test

* Handling the differences between HeadlessTextShaperStub and TextShaperImpl

* Make unit tests available

* Treat "\r\n" as a single unit.

* The backspace test against CRLF

* Backspace should treat CRLF as a unit.
pull/18827/head
Lin 3 months ago
committed by GitHub
parent
commit
e3dbedbcfa
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  2. 14
      src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
  3. 19
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  4. 23
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

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

@ -853,8 +853,13 @@ namespace Avalonia.Media.TextFormatting
{
var length = 0;
while (Codepoint.ReadAt(shapedRun.GlyphRun.Characters.Span, length, out var count) != Codepoint.ReplacementCodepoint)
while (Codepoint.ReadAt(shapedRun.GlyphRun.Characters.Span, length, out var count) is Codepoint codepoint && codepoint != Codepoint.ReplacementCodepoint)
{
if (codepoint.Value == 0x0D && Codepoint.ReadAt(shapedRun.GlyphRun.Characters.Span, length + count, out var lfCount).Value == 0x0A)
{
count += lfCount;
}
if (length + count >= runOffset)
{
break;
@ -869,7 +874,7 @@ namespace Avalonia.Media.TextFormatting
{
previousCharacterHit = shapedRun.GlyphRun.GetPreviousCaretCharacterHit(new CharacterHit(firstCluster + runOffset));
if(textSourceOffset > 0)
if (textSourceOffset > 0)
{
previousCharacterHit = new CharacterHit(textSourceOffset + previousCharacterHit.FirstCharacterIndex, previousCharacterHit.TrailingLength);
}

14
src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -174,6 +174,12 @@ namespace Avalonia.Headless
var codepoint = Codepoint.ReadAt(textSpan, i, out var count);
// Handle CRLF as a single cluster
if (codepoint.Value == 0x0D && Codepoint.ReadAt(textSpan, i + count, out var lfCount).Value == 0x0A)
{
count += lfCount;
}
var glyphIndex = typeface.GetGlyph(codepoint);
for (var j = 0; j < count; ++j)
@ -218,7 +224,7 @@ namespace Avalonia.Headless
return false;
}
public virtual bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
public virtual bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
{
glyphTypeface = null;
@ -238,9 +244,9 @@ namespace Avalonia.Headless
public virtual bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, out IGlyphTypeface glyphTypeface)
{
glyphTypeface = new HeadlessGlyphTypefaceImpl(
FontFamily.DefaultFontFamilyName,
fontSimulations.HasFlag(FontSimulations.Oblique) ? FontStyle.Italic : FontStyle.Normal,
fontSimulations.HasFlag(FontSimulations.Bold) ? FontWeight.Bold : FontWeight.Normal,
FontFamily.DefaultFontFamilyName,
fontSimulations.HasFlag(FontSimulations.Oblique) ? FontStyle.Italic : FontStyle.Normal,
fontSimulations.HasFlag(FontSimulations.Bold) ? FontWeight.Bold : FontWeight.Normal,
FontStretch.Normal);
TryCreateGlyphTypefaceCount++;

19
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -2129,6 +2129,25 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Backspace_Should_Delete_CRLFNewline_Character_At_Once()
{
using var _ = UnitTestApplication.Start(Services);
var target = new TextBox
{
Template = CreateTemplate(),
Text = $"First\r\nSecond",
CaretIndex = 7
};
target.ApplyTemplate();
// (First\r\nSecond)
RaiseKeyEvent(target, Key.Back, KeyModifiers.None);
// (FirstSecond)
Assert.Equal("FirstSecond", target.Text);
}
private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: () => new KeyboardNavigationHandler(),

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

@ -2285,6 +2285,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Backspace_Should_Treat_CRLF_As_A_Unit()
{
using (Start())
{
var typeface = new Typeface(FontFamily.Parse("resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#Manrope"));
var defaultProperties = new GenericTextRunProperties(typeface);
var textSource = new SingleBufferTextSource("one\r\n", defaultProperties);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
Assert.NotNull(textLine);
var backspaceHit = textLine.GetBackspaceCaretCharacterHit(new CharacterHit(5));
Assert.Equal(3, backspaceHit.FirstCharacterIndex);
}
}
private class FixedRunsTextSource : ITextSource
{
private readonly IReadOnlyList<TextRun> _textRuns;

Loading…
Cancel
Save