diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index a32a3e1b6c..da3a1f721c 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/src/Avalonia.Visuals/Media/GlyphRun.cs @@ -555,7 +555,7 @@ namespace Avalonia.Media } } - return new Rect(0, 0, width, height); + return new Rect(0, GlyphTypeface.Ascent * Scale, width, height); } private void Set(ref T field, T value) @@ -595,8 +595,6 @@ namespace Avalonia.Media _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width); var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; - - _bounds = new Rect(0, 0, width, height); } void IDisposable.Dispose() diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 8b44e32c48..08d9107bb1 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -181,6 +181,17 @@ namespace Avalonia.Media.TextFormatting return nextCharacterHit; } + if (characterHit.FirstCharacterIndex + characterHit.TrailingLength <= TextRange.Start + TextRange.Length) + { + return characterHit; // Can't move, we're after the last character + } + + var runIndex = GetRunIndexAtCodepointIndex(TextRange.End); + + var textRun = _textRuns[runIndex]; + + characterHit = textRun.GlyphRun.GetNextCaretCharacterHit(characterHit); + return characterHit; // Can't move, we're after the last character } @@ -192,6 +203,11 @@ namespace Avalonia.Media.TextFormatting return previousCharacterHit; } + if (characterHit.FirstCharacterIndex < TextRange.Start) + { + characterHit = new CharacterHit(TextRange.Start); + } + return characterHit; // Can't move, we're before the first character } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs index eaf4effdbe..bdf05c4f86 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs @@ -25,7 +25,7 @@ namespace Avalonia.Rendering.SceneGraph GlyphRun glyphRun, Point baselineOrigin, IDictionary childScenes = null) - : base(glyphRun.Bounds, transform) + : base(glyphRun.Bounds.Translate(baselineOrigin), transform) { Transform = transform; Foreground = foreground?.ToImmutable(); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs index c4d67deb4c..349143253e 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs @@ -24,7 +24,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { AvaloniaXamlIlDataContextTypeMetadataNode inferredDataContextTypeNode = null; AvaloniaXamlIlDataContextTypeMetadataNode directiveDataContextTypeNode = null; - bool isDataTemplate = on.Type.GetClrType().Equals(context.GetAvaloniaTypes().DataTemplate); for (int i = 0; i < on.Children.Count; ++i) { @@ -57,7 +56,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { inferredDataContextTypeNode = ParseDataContext(context, on, obj); } - else if(isDataTemplate + else if(context.GetAvaloniaTypes().DataTemplate.IsAssignableFrom(on.Type.GetClrType()) && pa.Property.Name == "DataType" && pa.Values[0] is XamlTypeExtensionNode dataTypeNode) { @@ -70,7 +69,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers // do more specialized inference if (directiveDataContextTypeNode is null) { - if (isDataTemplate && inferredDataContextTypeNode is null) + if (context.GetAvaloniaTypes().IDataTemplate.IsAssignableFrom(on.Type.GetClrType()) + && inferredDataContextTypeNode is null) { // Infer data type from collection binding on a control that displays items. var parentObject = context.ParentNodes().OfType().FirstOrDefault(); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index f4ca76c21c..3dec96dc43 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -41,6 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ResolveByNameExtension { get; } public IXamlType DataTemplate { get; } + public IXamlType IDataTemplate { get; } public IXamlType IItemsPresenterHost { get; } public IXamlType ItemsRepeater { get; } public IXamlType ReflectionBindingExtension { get; } @@ -98,6 +99,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension"); ResolveByNameExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ResolveByNameExtension"); DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate"); + IDataTemplate = cfg.TypeSystem.GetType("Avalonia.Controls.Templates.IDataTemplate"); IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost"); ItemsRepeater = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsRepeater"); ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension"); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 3655d78c9d..7abfe29f11 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using Avalonia.Media; using Avalonia.Media.TextFormatting; @@ -10,6 +9,65 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { public class TextLineTests { + private static readonly string s_multiLineText = "012345678\r\r0123456789"; + + [Fact] + public void Should_Get_First_CharacterHit() + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + + var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties); + + var formatter = new TextFormatterImpl(); + + var currentIndex = 0; + + while (currentIndex < s_multiLineText.Length) + { + var textLine = + formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + var firstCharacterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(int.MinValue)); + + Assert.Equal(textLine.TextRange.Start, firstCharacterHit.FirstCharacterIndex); + + currentIndex += textLine.TextRange.Length; + } + } + } + + [Fact] + public void Should_Get_Last_CharacterHit() + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + + var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties); + + var formatter = new TextFormatterImpl(); + + var currentIndex = 0; + + while (currentIndex < s_multiLineText.Length) + { + var textLine = + formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + var lastCharacterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(int.MaxValue)); + + Assert.Equal(textLine.TextRange.Start + textLine.TextRange.Length, + lastCharacterHit.FirstCharacterIndex + lastCharacterHit.TrailingLength); + + currentIndex += textLine.TextRange.Length; + } + } + } + [InlineData("𐐷𐐷𐐷𐐷𐐷")] [InlineData("𐐷1234")] [Theory]