Browse Source

Implement GlyphRun.BuildGeometry

pull/8313/head
Benedikt Stebner 4 years ago
parent
commit
7a533700d0
  1. 12
      samples/RenderDemo/Pages/GlyphRunPage.xaml
  2. 113
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  3. 19
      src/Avalonia.Base/Media/GlyphRun.cs
  4. 24
      src/Avalonia.Base/Media/PlatformGeometry.cs
  5. 8
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  6. 32
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  7. 28
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  8. 66
      tests/Avalonia.RenderTests/Media/GlyphRunTests.cs
  9. BIN
      tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png
  10. BIN
      tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png

12
samples/RenderDemo/Pages/GlyphRunPage.xaml

@ -2,13 +2,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RenderDemo.Pages"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.GlyphRunPage">
<Border
<Grid
ColumnDefinitions="*,*"
Background="White">
<Image
x:Name="imageControl"
Stretch="None">
</Image>
</Border>
<local:GlyphRunControl Grid.Column="0"/>
<local:GlyphRunGeometryControl Grid.Column="1"/>
</Grid>
</UserControl>

113
samples/RenderDemo/Pages/GlyphRunPage.xaml.cs

@ -9,14 +9,6 @@ namespace RenderDemo.Pages
{
public class GlyphRunPage : UserControl
{
private Image _imageControl;
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];
private float _fontSize = 20;
private int _direction = 10;
public GlyphRunPage()
{
this.InitializeComponent();
@ -25,19 +17,43 @@ namespace RenderDemo.Pages
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
public class GlyphRunControl : Control
{
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];
private float _fontSize = 20;
private int _direction = 10;
_imageControl = this.FindControl<Image>("imageControl");
_imageControl.Source = new DrawingImage();
private DispatcherTimer _timer;
DispatcherTimer.Run(() =>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_timer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
_timer.Tick += (s,e) =>
{
UpdateGlyphRun();
InvalidateVisual();
};
_timer.Start();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_timer.Stop();
return true;
}, TimeSpan.FromSeconds(1));
_timer = null;
}
private void UpdateGlyphRun()
public override void Render(DrawingContext context)
{
var c = (char)_rand.Next(65, 90);
@ -57,27 +73,70 @@ namespace RenderDemo.Pages
_characters[0] = c;
var scale = (double)_fontSize / _glyphTypeface.DesignEmHeight;
var glyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices);
var drawingGroup = new DrawingGroup();
context.DrawGlyphRun(Brushes.Black, glyphRun);
}
}
var glyphRunDrawing = new GlyphRunDrawing
public class GlyphRunGeometryControl : Control
{
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];
private float _fontSize = 20;
private int _direction = 10;
private DispatcherTimer _timer;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_timer = new DispatcherTimer
{
Foreground = Brushes.Black,
GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices)
Interval = TimeSpan.FromSeconds(1)
};
drawingGroup.Children.Add(glyphRunDrawing);
var geometryDrawing = new GeometryDrawing
_timer.Tick += (s, e) =>
{
Pen = new Pen(Brushes.Black),
Geometry = new RectangleGeometry { Rect = new Rect(glyphRunDrawing.GlyphRun.Size) }
InvalidateVisual();
};
drawingGroup.Children.Add(geometryDrawing);
_timer.Start();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_timer.Stop();
_timer = null;
}
public override void Render(DrawingContext context)
{
var c = (char)_rand.Next(65, 90);
if (_fontSize + _direction > 200)
{
_direction = -10;
}
if (_fontSize + _direction < 20)
{
_direction = 10;
}
_fontSize += _direction;
_glyphIndices[0] = _glyphTypeface.GetGlyph(c);
_characters[0] = c;
var glyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices);
var geometry = glyphRun.BuildGeometry();
(_imageControl.Source as DrawingImage).Drawing = drawingGroup;
context.DrawGeometry(Brushes.Green, null, geometry);
}
}
}

19
src/Avalonia.Base/Media/GlyphRun.cs

@ -194,6 +194,25 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Obtains geometry for the glyph run.
/// </summary>
/// <returns>The geometry returned contains the combined geometry of all glyphs in the glyph run.</returns>
public Geometry BuildGeometry()
{
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var geometryImpl = platformRenderInterface.BuildGlyphRunGeometry(this, out var scale);
var geometry = new PlatformGeometry(geometryImpl);
var transform = new MatrixTransform(Matrix.CreateTranslation(geometry.Bounds.Left, -geometry.Bounds.Top) * scale);
geometry.Transform = transform;
return geometry;
}
/// <summary>
/// Retrieves the offset from the leading edge of the <see cref="GlyphRun"/>
/// to the leading or trailing edge of a caret stop containing the specified character hit.

24
src/Avalonia.Base/Media/PlatformGeometry.cs

@ -0,0 +1,24 @@
using Avalonia.Platform;
namespace Avalonia.Media
{
internal class PlatformGeometry : Geometry
{
private readonly IGeometryImpl _geometryImpl;
public PlatformGeometry(IGeometryImpl geometryImpl)
{
_geometryImpl = geometryImpl;
}
public override Geometry Clone()
{
return new PlatformGeometry(_geometryImpl);
}
protected override IGeometryImpl? CreateDefiningGeometry()
{
return _geometryImpl;
}
}
}

8
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -58,6 +58,14 @@ namespace Avalonia.Platform
/// <returns>A combined geometry.</returns>
IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2);
/// <summary>
/// Created a geometry implementation for the glyph run.
/// </summary>
/// <param name="glyphRun">The glyph run to build a geometry from.</param>
/// <param name="scale">The scaling of the produces geometry.</param>
/// <returns>The geometry returned contains the combined geometry of all glyphs in the glyph run.</returns>
IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale);
/// <summary>
/// Creates a renderer.
/// </summary>

32
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -62,6 +62,38 @@ namespace Avalonia.Skia
return new CombinedGeometryImpl(combineMode, g1, g2);
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
{
if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
{
throw new InvalidOperationException("PlatformImpl can't be null.");
}
var fontRenderingEmSize = (float)glyphRun.FontRenderingEmSize;
var glyphs = glyphRun.GlyphIndices.ToArray();
var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize)
{
Size = fontRenderingEmSize,
Edging = SKFontEdging.Antialias,
Hinting = SKFontHinting.None,
LinearMetrics = true
};
SKPath path = null;
var matrix = SKMatrix.Identity;
skFont.GetGlyphPaths(glyphs, (p, m) =>
{
matrix = m;
path = p;
});
scale = Matrix.CreateScale(matrix.ScaleX, matrix.ScaleY);
return new StreamGeometryImpl(path);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmap(string fileName)
{

28
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -159,6 +159,34 @@ namespace Avalonia.Direct2D1
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children) => new GeometryGroupImpl(fillRule, children);
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2);
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
{
if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
{
throw new InvalidOperationException("PlatformImpl can't be null.");
}
var pathGeometry = new SharpDX.Direct2D1.PathGeometry(Direct2D1Factory);
using (var sink = pathGeometry.Open())
{
var glyphs = new short[glyphRun.GlyphIndices.Count];
for (int i = 0; i < glyphRun.GlyphIndices.Count; i++)
{
glyphs[i] = (short)glyphRun.GlyphIndices[i];
}
glyphTypeface.FontFace.GetGlyphRunOutline((float)glyphRun.FontRenderingEmSize, glyphs, null, null, false, !glyphRun.IsLeftToRight, sink);
sink.Close();
}
scale = Matrix.Identity;
return new StreamGeometryImpl(pathGeometry);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmap(string fileName)
{

66
tests/Avalonia.RenderTests/Media/GlyphRunTests.cs

@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Xunit;
#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
#else
namespace Avalonia.Direct2D1.RenderTests.Media
#endif
{
public class GlyphRunTests : TestBase
{
public GlyphRunTests()
: base(@"Media\GlyphRun")
{
}
[Fact]
public async Task Should_Render_GlyphRun_Geometry()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 80,
Height = 90,
Child = new GlyphRunGeometryControl()
};
await RenderToFile(target);
CompareImages();
}
public class GlyphRunGeometryControl : Control
{
private readonly Geometry _geometry;
public GlyphRunGeometryControl()
{
var glyphTypeface = Typeface.Default.GlyphTypeface;
var glyphIndices = new[] { glyphTypeface.GetGlyph('A') };
var characters = new[] { 'A' };
var glyphRun = new GlyphRun(glyphTypeface, 100, characters, glyphIndices);
_geometry = glyphRun.BuildGeometry();
}
protected override Size MeasureOverride(Size availableSize)
{
return _geometry.Bounds.Size;
}
public override void Render(DrawingContext context)
{
context.DrawGeometry(Brushes.Green, null, _geometry);
}
}
}
}

BIN
tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Loading…
Cancel
Save