Browse Source

Merge pull request #9896 from Mikolaytis/BetterUnderline

[Text] Underline with intercepts
Benedikt Stebner 2 years ago
parent
commit
a4d3f43176

+ 51 - 1
src/Avalonia.Base/Media/TextDecoration.cs

@@ -1,5 +1,10 @@
-using Avalonia.Collections;
+using System;
+using System.Collections.Generic;
+using Avalonia.Collections;
+using Avalonia.Collections.Pooled;
 using Avalonia.Media.TextFormatting;
+using Avalonia.Platform;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media
 {
@@ -209,6 +214,51 @@ namespace Avalonia.Media
             var pen = new Pen(Stroke ?? defaultBrush, thickness,
                 new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap);
 
+            if (Location != TextDecorationLocation.Strikethrough)
+            {
+                var offsetY = glyphRun.BaselineOrigin.Y - origin.Y;
+
+                var intersections = glyphRun.GlyphRunImpl.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY));
+
+                if (intersections != null && intersections.Count > 0)
+                {
+                    var last = baselineOrigin.X;
+                    var finalPos = last + glyphRun.Size.Width;
+                    var end = last;
+
+                    var points = new List<double>();
+
+                    //math is taken from chrome's source code.
+                    for (var i = 0; i < intersections.Count; i += 2)
+                    {
+                        var start = intersections[i] - thickness;
+                        end = intersections[i + 1] + thickness;
+                        if (start > last && last + textMetrics.FontRenderingEmSize / 12 < start)
+                        {
+                            points.Add(last);
+                            points.Add(start);
+                        }
+                        last = end;
+                    }
+
+                    if (end < finalPos)
+                    {
+                        points.Add(end);
+                        points.Add(finalPos);
+                    }
+
+                    for (var i = 0; i < points.Count; i += 2)
+                    {
+                        var a = new Point(points[i], origin.Y);
+                        var b = new Point(points[i + 1], origin.Y);
+
+                        drawingContext.DrawLine(pen, a, b);
+                    }
+
+                    return;
+                }
+            }
+
             drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Metrics.Width, 0));
         }
     }

+ 10 - 3
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@@ -1436,6 +1436,13 @@ namespace Avalonia.Media.TextFormatting
 
             var lineHeight = _paragraphProperties.LineHeight;
 
+            var lastRunIndex = _textRuns.Count - 1;
+
+            if (lastRunIndex > 0 && _textRuns[lastRunIndex] is TextEndOfLine)
+            {
+                lastRunIndex--;
+            }
+
             for (var index = 0; index < _textRuns.Count; index++)
             {
                 switch (_textRuns[index])
@@ -1470,7 +1477,7 @@ namespace Avalonia.Media.TextFormatting
                                 }
                             }
 
-                            if (index == _textRuns.Count - 1)
+                            if (index == lastRunIndex)
                             {
                                 width = widthIncludingWhitespace + textRun.GlyphRun.Metrics.Width;
                                 trailingWhitespaceLength = textRun.GlyphRun.Metrics.TrailingWhitespaceLength;
@@ -1490,7 +1497,7 @@ namespace Avalonia.Media.TextFormatting
                             {
                                 case FlowDirection.LeftToRight:
                                     {
-                                        if (index == _textRuns.Count - 1)
+                                        if (index == lastRunIndex)
                                         {
                                             width = widthIncludingWhitespace;
                                             trailingWhitespaceLength = 0;
@@ -1502,7 +1509,7 @@ namespace Avalonia.Media.TextFormatting
 
                                 case FlowDirection.RightToLeft:
                                     {
-                                        if (index == _textRuns.Count - 1)
+                                        if (index == lastRunIndex)
                                         {
                                             width = widthIncludingWhitespace;
                                             trailingWhitespaceLength = 0;

+ 5 - 1
src/Avalonia.Base/Platform/IGlyphRunImpl.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using Avalonia.Metadata;
 
 namespace Avalonia.Platform
@@ -7,5 +8,8 @@ namespace Avalonia.Platform
     ///     Actual implementation of a glyph run that stores platform dependent resources.
     /// </summary>
     [Unstable]
-    public interface IGlyphRunImpl : IDisposable { }
+    public interface IGlyphRunImpl : IDisposable 
+    {
+        IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound);
+    }
 }

+ 5 - 0
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@@ -128,6 +128,11 @@ namespace Avalonia.Headless
             public void Dispose()
             {
             }
+
+            public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
+            {
+                throw new NotImplementedException();
+            }
         }
 
         class HeadlessGeometryStub : IGeometryImpl

+ 4 - 0
src/Skia/Avalonia.Skia/GlyphRunImpl.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using Avalonia.Metadata;
 using Avalonia.Platform;
 using SkiaSharp;
@@ -20,6 +21,9 @@ namespace Avalonia.Skia
         /// </summary>
         public SKTextBlob TextBlob { get; }
 
+        public IReadOnlyList<float> GetIntersections(float upperBound, float lowerBound) => 
+            TextBlob.GetIntercepts(lowerBound, upperBound);
+
         void IDisposable.Dispose()
         {
             TextBlob.Dispose();

+ 7 - 1
src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs

@@ -1,4 +1,5 @@
-using Avalonia.Platform;
+using System.Collections.Generic;
+using Avalonia.Platform;
 
 namespace Avalonia.Direct2D1.Media
 {
@@ -15,5 +16,10 @@ namespace Avalonia.Direct2D1.Media
         {
             GlyphRun?.Dispose();
         }
+
+        public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
+        {
+            return null;
+        }
     }
 }

+ 7 - 1
tests/Avalonia.Benchmarks/NullGlyphRun.cs

@@ -1,4 +1,5 @@
-using Avalonia.Platform;
+using System.Collections.Generic;
+using Avalonia.Platform;
 
 namespace Avalonia.Benchmarks
 {
@@ -7,5 +8,10 @@ namespace Avalonia.Benchmarks
         public void Dispose()
         {
         }
+
+        public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
+        {
+            return null;
+        }
     }
 }

+ 7 - 1
tests/Avalonia.UnitTests/MockGlyphRun.cs

@@ -1,4 +1,5 @@
-using Avalonia.Platform;
+using System.Collections.Generic;
+using Avalonia.Platform;
 
 namespace Avalonia.UnitTests
 {
@@ -8,5 +9,10 @@ namespace Avalonia.UnitTests
         {
             
         }
+
+        public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
+        {
+            return null;
+        }
     }
 }