diff --git a/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs b/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
index 97a9320c95..088a063690 100644
--- a/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
+++ b/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
@@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
+using Avalonia.Media.Immutable;
namespace RenderDemo.Pages
{
@@ -59,6 +60,10 @@ namespace RenderDemo.Pages
var geometry = formattedText.BuildGeometry(new Point(10 + formattedText.Width + 10, 0));
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);
}
}
}
diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml
index 43d93a9315..6929f192c7 100644
--- a/samples/Sandbox/MainWindow.axaml
+++ b/samples/Sandbox/MainWindow.axaml
@@ -1,5 +1,4 @@
-
diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs
index 5480336f84..27d99bdc10 100644
--- a/src/Avalonia.Base/Media/FormattedText.cs
+++ b/src/Avalonia.Base/Media/FormattedText.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
+using Avalonia.Controls;
using Avalonia.Media.TextFormatting;
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
private TextLineBreak? _previousLineBreak;
+ private int _position;
+ private int _length;
internal LineEnumerator(FormattedText text)
{
_previousHeight = 0;
- Length = 0;
+ _length = 0;
_previousLineBreak = null;
- Position = 0;
+ _position = 0;
_lineCount = 0;
_totalHeight = 0;
Current = null;
@@ -678,9 +682,17 @@ namespace Avalonia.Media
_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;
+ }
///
/// Gets the current text line in the collection
@@ -1292,6 +1304,92 @@ namespace Avalonia.Media
return accumulatedGeometry;
}
+ ///
+ /// Builds a highlight geometry object.
+ ///
+ /// The origin of the highlight region
+ /// Geometry that surrounds the text.
+ public Geometry? BuildHighlightGeometry(Point origin)
+ {
+ return BuildHighlightGeometry(origin, 0, _text.Length);
+ }
+
+ ///
+ /// Builds a highlight geometry object for a given character range.
+ ///
+ /// The origin of the highlight region.
+ /// The start index of initial character the bounds should be obtained for.
+ /// The number of characters the bounds should be obtained for.
+ /// Geometry that surrounds the specified character range.
+ 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;
+ }
+
///
/// Draws the text object
///
diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs
index 76c67a5cf4..c31a6699c2 100644
--- a/src/Avalonia.Base/Media/Geometry.cs
+++ b/src/Avalonia.Base/Media/Geometry.cs
@@ -185,5 +185,18 @@ namespace Avalonia.Media
var control = e.Sender as Geometry;
control?.InvalidateGeometry();
}
+
+ ///
+ /// Combines the two geometries using the specified and applies the specified transform to the resulting geometry.
+ ///
+ /// The first geometry to combine.
+ /// The second geometry to combine.
+ /// One of the enumeration values that specifies how the geometries are combined.
+ /// A transformation to apply to the combined geometry, or null.
+ ///
+ public static Geometry Combine(Geometry geometry1, RectangleGeometry geometry2, GeometryCombineMode combineMode, Transform? transform = null)
+ {
+ return new CombinedGeometry(combineMode, geometry1, geometry2, transform);
+ }
}
}