浏览代码

Implement FormattedText.BuildHighlightGeometry

Benedikt Stebner 3 年之前
父节点
当前提交
116fd47439

+ 5 - 0
samples/RenderDemo/Pages/FormattedTextPage.axaml.cs

@@ -3,6 +3,7 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media;
 using Avalonia.Media;
+using Avalonia.Media.Immutable;
 
 
 namespace RenderDemo.Pages
 namespace RenderDemo.Pages
 {
 {
@@ -59,6 +60,10 @@ namespace RenderDemo.Pages
             var geometry = formattedText.BuildGeometry(new Point(10 + formattedText.Width + 10, 0));
             var geometry = formattedText.BuildGeometry(new Point(10 + formattedText.Width + 10, 0));
 
 
             context.DrawGeometry(gradient, null, geometry);
             context.DrawGeometry(gradient, null, geometry);
+
+            var highlightGeometry = formattedText.BuildHighlightGeometry(new Point(10 + formattedText.Width + 10, 0));
+
+            context.DrawGeometry(null, new ImmutablePen(gradient.ToImmutable(), 2), highlightGeometry);
         }
         }
     }
     }
 }
 }

+ 0 - 1
samples/Sandbox/MainWindow.axaml

@@ -1,5 +1,4 @@
 <Window xmlns="https://github.com/avaloniaui"
 <Window xmlns="https://github.com/avaloniaui"
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
         x:Class="Sandbox.MainWindow">
         x:Class="Sandbox.MainWindow">
-    <TextBox />
 </Window>
 </Window>

+ 102 - 4
src/Avalonia.Base/Media/FormattedText.cs

@@ -1,8 +1,10 @@
 using System;
 using System;
 using System.Collections;
 using System.Collections;
+using System.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Globalization;
 using System.Globalization;
+using Avalonia.Controls;
 using Avalonia.Media.TextFormatting;
 using Avalonia.Media.TextFormatting;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 
 
@@ -654,14 +656,16 @@ namespace Avalonia.Media
 
 
             // line break before _currentLine, needed in case we have to reformat it with collapsing symbol
             // line break before _currentLine, needed in case we have to reformat it with collapsing symbol
             private TextLineBreak? _previousLineBreak;
             private TextLineBreak? _previousLineBreak;
+            private int _position;
+            private int _length;
 
 
             internal LineEnumerator(FormattedText text)
             internal LineEnumerator(FormattedText text)
             {
             {
                 _previousHeight = 0;
                 _previousHeight = 0;
-                Length = 0;
+                _length = 0;
                 _previousLineBreak = null;
                 _previousLineBreak = null;
 
 
-                Position = 0;
+                _position = 0;
                 _lineCount = 0;
                 _lineCount = 0;
                 _totalHeight = 0;
                 _totalHeight = 0;
                 Current = null;
                 Current = null;
@@ -678,9 +682,17 @@ namespace Avalonia.Media
                 _nextLine = null;
                 _nextLine = null;
             }
             }
 
 
-            private int Position { get; set; }
+            public int Position 
+            { 
+                get => _position; 
+                private set => _position = value;
+            }
 
 
-            private int Length { get; set; }
+            public int Length 
+            { 
+                get => _length; 
+                private set => _length = value; 
+            }
 
 
             /// <summary>
             /// <summary>
             /// Gets the current text line in the collection
             /// Gets the current text line in the collection
@@ -1292,6 +1304,92 @@ namespace Avalonia.Media
             return accumulatedGeometry;
             return accumulatedGeometry;
         }
         }
 
 
+        /// <summary>
+        /// Builds a highlight geometry object.
+        /// </summary>
+        /// <param name="origin">The origin of the highlight region</param>
+        /// <returns>Geometry that surrounds the text.</returns>
+        public Geometry? BuildHighlightGeometry(Point origin)
+        {
+            return BuildHighlightGeometry(origin, 0, _text.Length);
+        }
+
+        /// <summary>
+        /// Builds a highlight geometry object for a given character range.
+        /// </summary>
+        /// <param name="origin">The origin of the highlight region.</param>
+        /// <param name="startIndex">The start index of initial character the bounds should be obtained for.</param>
+        /// <param name="count">The number of characters the bounds should be obtained for.</param>
+        /// <returns>Geometry that surrounds the specified character range.</returns>
+        public Geometry? BuildHighlightGeometry(Point origin, int startIndex, int count)
+        {
+            ValidateRange(startIndex, count);
+
+            Geometry? accumulatedBounds = null;
+
+            using (var enumerator = GetEnumerator())
+            {
+                var lineOrigin = origin;
+
+                while (enumerator.MoveNext())
+                {
+                    var currentLine = enumerator.Current!;
+
+                    int x0 = Math.Max(enumerator.Position, startIndex);
+                    int x1 = Math.Min(enumerator.Position + enumerator.Length, startIndex + count);
+
+                    // check if this line is intersects with the specified character range
+                    if (x0 < x1)
+                    {
+                        var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0);
+
+                        if (highlightBounds != null)
+                        {
+                            foreach (var bound in highlightBounds)
+                            {
+                                var rect = bound.Rectangle;
+
+                                if (FlowDirection == FlowDirection.RightToLeft)
+                                {
+                                    // Convert logical units (which extend leftward from the right edge
+                                    // of the paragraph) to physical units.
+                                    //
+                                    // Note that since rect is in logical units, rect.Right corresponds to 
+                                    // the visual *left* edge of the rectangle in the RTL case. Specifically,
+                                    // is the distance leftward from the right edge of the formatting rectangle
+                                    // whose width is the paragraph width passed to FormatLine.
+                                    //
+                                    rect = rect.WithX(enumerator.CurrentParagraphWidth - rect.Right);
+                                }
+
+                                rect = new Rect(new Point(rect.X + lineOrigin.X, rect.Y + lineOrigin.Y), rect.Size);
+
+                                RectangleGeometry rectangleGeometry = new RectangleGeometry(rect);
+
+                                if (accumulatedBounds == null)
+                                {
+                                    accumulatedBounds = rectangleGeometry;
+                                }
+                                else
+                                {
+                                    accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union);
+                                }                                  
+                            }
+                        }
+                    }
+
+                    AdvanceLineOrigin(ref lineOrigin, currentLine);
+                }
+            }
+
+            if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsEmpty)
+            {
+                return null;
+            }            
+
+            return accumulatedBounds;
+        }
+
         /// <summary>
         /// <summary>
         /// Draws the text object
         /// Draws the text object
         /// </summary>
         /// </summary>

+ 13 - 0
src/Avalonia.Base/Media/Geometry.cs

@@ -185,5 +185,18 @@ namespace Avalonia.Media
             var control = e.Sender as Geometry;
             var control = e.Sender as Geometry;
             control?.InvalidateGeometry();
             control?.InvalidateGeometry();
         }
         }
+
+        /// <summary>
+        /// Combines the two geometries using the specified <see cref="GeometryCombineMode"/> and applies the specified transform to the resulting geometry.
+        /// </summary>
+        /// <param name="geometry1">The first geometry to combine.</param>
+        /// <param name="geometry2">The second geometry to combine.</param>
+        /// <param name="combineMode">One of the enumeration values that specifies how the geometries are combined.</param>
+        /// <param name="transform">A transformation to apply to the combined geometry, or <c>null</c>.</param>
+        /// <returns></returns>
+        public static Geometry Combine(Geometry geometry1, RectangleGeometry geometry2, GeometryCombineMode combineMode, Transform? transform = null)
+        {
+            return new CombinedGeometry(combineMode, geometry1, geometry2, transform);
+        }
     }
     }
 }
 }