Browse Source

Merge pull request #9896 from Mikolaytis/BetterUnderline

[Text] Underline with intercepts
pull/9968/head
Benedikt Stebner 4 years ago
committed by GitHub
parent
commit
a4d3f43176
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 52
      src/Avalonia.Base/Media/TextDecoration.cs
  2. 13
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  3. 6
      src/Avalonia.Base/Platform/IGlyphRunImpl.cs
  4. 5
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  5. 4
      src/Skia/Avalonia.Skia/GlyphRunImpl.cs
  6. 8
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
  7. 8
      tests/Avalonia.Benchmarks/NullGlyphRun.cs
  8. 8
      tests/Avalonia.UnitTests/MockGlyphRun.cs

52
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));
}
}

13
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;

6
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
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
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();

8
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;
}
}
}

8
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;
}
}
}

8
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;
}
}
}

Loading…
Cancel
Save