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.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user