Browse Source

Rework GlyphTypeface

pull/9153/head
Benedikt Stebner 4 years ago
parent
commit
5a5080b1a5
  1. 4
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  2. 12
      src/Avalonia.Base/Media/FontManager.cs
  3. 63
      src/Avalonia.Base/Media/FontMetrics.cs
  4. 18
      src/Avalonia.Base/Media/GlyphRun.cs
  5. 130
      src/Avalonia.Base/Media/GlyphTypeface.cs
  6. 73
      src/Avalonia.Base/Media/IGlyphTypeface.cs
  7. 16
      src/Avalonia.Base/Media/TextDecoration.cs
  8. 6
      src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
  9. 9
      src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs
  10. 26
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  11. 24
      src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs
  12. 4
      src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
  13. 5
      src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
  14. 2
      src/Avalonia.Base/Media/Typeface.cs
  15. 2
      src/Avalonia.Base/Platform/IFontManagerImpl.cs
  16. 6
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  17. 2
      src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
  18. 2
      src/Avalonia.Base/Utilities/ReadOnlySlice.cs
  19. 22
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  20. 2
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  21. 92
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  22. 18
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  23. 2
      src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
  24. 105
      src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
  25. 2
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  26. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  27. 4
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  28. 2
      tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
  29. 96
      tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
  30. 2
      tests/Avalonia.UnitTests/MockFontManagerImpl.cs
  31. 34
      tests/Avalonia.UnitTests/MockGlyphTypeface.cs

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

@ -22,7 +22,7 @@ namespace RenderDemo.Pages
public class GlyphRunControl : Control
{
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];
@ -81,7 +81,7 @@ namespace RenderDemo.Pages
public class GlyphRunGeometryControl : Control
{
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];

12
src/Avalonia.Base/Media/FontManager.cs

@ -13,8 +13,8 @@ namespace Avalonia.Media
/// </summary>
public sealed class FontManager
{
private readonly ConcurrentDictionary<Typeface, GlyphTypeface> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, GlyphTypeface>();
private readonly ConcurrentDictionary<Typeface, IGlyphTypeface> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, IGlyphTypeface>();
private readonly FontFamily _defaultFontFamily;
private readonly IReadOnlyList<FontFallback>? _fontFallbacks;
@ -81,13 +81,13 @@ namespace Avalonia.Media
PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
/// <summary>
/// Returns a new <see cref="GlyphTypeface"/>, or an existing one if a matching <see cref="GlyphTypeface"/> exists.
/// Returns a new <see cref="IGlyphTypeface"/>, or an existing one if a matching <see cref="GlyphTypeface"/> exists.
/// </summary>
/// <param name="typeface">The typeface.</param>
/// <returns>
/// The <see cref="GlyphTypeface"/>.
/// The <see cref="IGlyphTypeface"/>.
/// </returns>
public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
{
while (true)
{
@ -96,7 +96,7 @@ namespace Avalonia.Media
return glyphTypeface;
}
glyphTypeface = new GlyphTypeface(typeface);
glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface);
if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface))
{

63
src/Avalonia.Base/Media/FontMetrics.cs

@ -0,0 +1,63 @@
namespace Avalonia.Media
{
/// <summary>
/// The font metrics is holding information about a font's ascent, descent, etc. in design em units.
/// </summary>
public readonly struct FontMetrics
{
/// <summary>
/// Gets the font design units per em.
/// </summary>
public short DesignEmHeight { get; init; }
/// <summary>
/// A <see cref="bool"/> value indicating whether all glyphs in the font have the same advancement.
/// </summary>
public bool IsFixedPitch { get; init; }
/// <summary>
/// Gets the recommended distance above the baseline in design em size.
/// </summary>
public int Ascent { get; init; }
/// <summary>
/// Gets the recommended distance under the baseline in design em size.
/// </summary>
public int Descent { get; init; }
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
public int LineGap { get; init; }
/// <summary>
/// Gets the recommended line spacing of a formed text line.
/// </summary>
public int LineSpacing => Descent - Ascent + LineGap;
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
public int UnderlinePosition { get; init; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int UnderlineThickness { get; init; }
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
public int StrikethroughPosition { get; init; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int StrikethroughThickness { get; init; }
}
}
namespace System.Runtime.CompilerServices
{
public class IsExternalInit { }
}

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

@ -16,7 +16,7 @@ namespace Avalonia.Media
private static readonly IComparer<int> s_descendingComparer = new ReverseComparer<int>();
private IGlyphRunImpl? _glyphRunImpl;
private GlyphTypeface _glyphTypeface;
private IGlyphTypeface _glyphTypeface;
private double _fontRenderingEmSize;
private int _biDiLevel;
private Point? _baselineOrigin;
@ -43,7 +43,7 @@ namespace Avalonia.Media
/// <param name="glyphClusters">The glyph clusters.</param>
/// <param name="biDiLevel">The bidi level.</param>
public GlyphRun(
GlyphTypeface glyphTypeface,
IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
ReadOnlySlice<char> characters,
IReadOnlyList<ushort> glyphIndices,
@ -70,9 +70,9 @@ namespace Avalonia.Media
}
/// <summary>
/// Gets the <see cref="Media.GlyphTypeface"/> for the <see cref="GlyphRun"/>.
/// Gets the <see cref="IGlyphTypeface"/> for the <see cref="GlyphRun"/>.
/// </summary>
public GlyphTypeface GlyphTypeface => _glyphTypeface;
public IGlyphTypeface GlyphTypeface => _glyphTypeface;
/// <summary>
/// Gets or sets the em size used for rendering the <see cref="GlyphRun"/>.
@ -172,7 +172,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets the scale of the current <see cref="Media.GlyphTypeface"/>
/// </summary>
internal double Scale => FontRenderingEmSize / GlyphTypeface.DesignEmHeight;
internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight;
/// <summary>
/// Returns <c>true</c> if the text direction is left-to-right. Otherwise, returns <c>false</c>.
@ -613,7 +613,7 @@ namespace Avalonia.Media
/// <returns>The baseline origin.</returns>
private Point CalculateBaselineOrigin()
{
return new Point(0, -GlyphTypeface.Ascent * Scale);
return new Point(0, -GlyphTypeface.Metrics.Ascent * Scale);
}
private GlyphRunMetrics CreateGlyphRunMetrics()
@ -637,7 +637,7 @@ namespace Avalonia.Media
}
var isReversed = firstCluster > lastCluster;
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
var height = GlyphTypeface.Metrics.LineSpacing * Scale;
var widthIncludingTrailingWhitespace = 0d;
var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount);
@ -864,11 +864,11 @@ namespace Avalonia.Media
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var count = GlyphIndices.Count;
var scale = (float)(FontRenderingEmSize / GlyphTypeface.DesignEmHeight);
var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight);
if (GlyphOffsets == null)
{
if (GlyphTypeface.IsFixedPitch)
if (GlyphTypeface.Metrics.IsFixedPitch)
{
var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);

130
src/Avalonia.Base/Media/GlyphTypeface.cs

@ -1,130 +0,0 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Media
{
public sealed class GlyphTypeface : IDisposable
{
public GlyphTypeface(Typeface typeface)
: this(FontManager.Current.PlatformImpl.CreateGlyphTypeface(typeface))
{
}
public GlyphTypeface(IGlyphTypefaceImpl platformImpl)
{
PlatformImpl = platformImpl;
}
public IGlyphTypefaceImpl PlatformImpl { get; }
/// <summary>
/// Gets the font design units per em.
/// </summary>
public short DesignEmHeight => PlatformImpl.DesignEmHeight;
/// <summary>
/// Gets the recommended distance above the baseline in design em size.
/// </summary>
public int Ascent => PlatformImpl.Ascent;
/// <summary>
/// Gets the recommended distance under the baseline in design em size.
/// </summary>
public int Descent => PlatformImpl.Descent;
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
public int LineGap => PlatformImpl.LineGap;
/// <summary>
/// Gets the recommended line height.
/// </summary>
public int LineHeight => Descent - Ascent + LineGap;
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
public int UnderlinePosition => PlatformImpl.UnderlinePosition;
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int UnderlineThickness => PlatformImpl.UnderlineThickness;
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
public int StrikethroughPosition => PlatformImpl.StrikethroughPosition;
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int StrikethroughThickness => PlatformImpl.StrikethroughThickness;
/// <summary>
/// A <see cref="bool"/> value indicating whether all glyphs in the font have the same advancement.
/// </summary>
public bool IsFixedPitch => PlatformImpl.IsFixedPitch;
/// <summary>
/// Gets the number of glyphs held by this glyph typeface.
/// </summary>
public int GlyphCount => PlatformImpl.GlyphCount;
/// <summary>
/// Returns an glyph index for the specified codepoint.
/// </summary>
/// <remarks>
/// Returns a replacement glyph if a glyph isn't found.
/// </remarks>
/// <param name="codepoint">The codepoint.</param>
/// <returns>
/// A glyph index.
/// </returns>
public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint);
/// <summary>
/// Tries to get an glyph index for specified codepoint.
/// </summary>
/// <param name="codepoint">The codepoint.</param>
/// <param name="glyph">A glyph index.</param>
/// <returns>
/// <c>true</c> if an glyph index was found, <c>false</c> otherwise.
/// </returns>
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = PlatformImpl.GetGlyph(codepoint);
return glyph != 0;
}
/// <summary>
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
/// </summary>
/// <param name="codepoints">The codepoints to map.</param>
/// <returns></returns>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) => PlatformImpl.GetGlyphs(codepoints);
/// <summary>
/// Returns the glyph advance for the specified glyph.
/// </summary>
/// <param name="glyph">The glyph.</param>
/// <returns>
/// The advance.
/// </returns>
public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph);
/// <summary>
/// Returns an array of glyph advances in design em size.
/// </summary>
/// <param name="glyphs">The glyph indices.</param>
/// <returns></returns>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) => PlatformImpl.GetGlyphAdvances(glyphs);
void IDisposable.Dispose()
{
PlatformImpl?.Dispose();
}
}
}

73
src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs → src/Avalonia.Base/Media/IGlyphTypeface.cs

@ -1,60 +1,23 @@
using System;
using Avalonia.Metadata;
namespace Avalonia.Platform
namespace Avalonia.Media
{
[Unstable]
public interface IGlyphTypefaceImpl : IDisposable
public interface IGlyphTypeface : IDisposable
{
/// <summary>
/// Gets the font design units per em.
/// </summary>
short DesignEmHeight { get; }
/// <summary>
/// Gets the recommended distance above the baseline in design em size.
/// </summary>
int Ascent { get; }
/// <summary>
/// Gets the recommended distance under the baseline in design em size.
/// </summary>
int Descent { get; }
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
int LineGap { get; }
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
int UnderlinePosition { get; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
int UnderlineThickness { get; }
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
int StrikethroughPosition { get; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
int StrikethroughThickness { get; }
/// <summary>
/// A <see cref="bool"/> value indicating whether all glyphs in the font have the same advancement.
/// Gets the number of glyphs held by this glyph typeface.
/// </summary>
bool IsFixedPitch { get; }
int GlyphCount { get; }
/// <summary>
/// Gets the number of glyphs held by this glyph typeface.
/// Gets the font metrics.
/// </summary>
int GlyphCount { get; }
/// <returns>
/// The font metrics.
/// </returns>
FontMetrics Metrics { get; }
/// <summary>
/// Returns an glyph index for the specified codepoint.
@ -68,6 +31,16 @@ namespace Avalonia.Platform
/// </returns>
ushort GetGlyph(uint codepoint);
/// <summary>
/// Tries to get an glyph index for specified codepoint.
/// </summary>
/// <param name="codepoint">The codepoint.</param>
/// <param name="glyph">A glyph index.</param>
/// <returns>
/// <c>true</c> if an glyph index was found, <c>false</c> otherwise.
/// </returns>
bool TryGetGlyph(uint codepoint, out ushort glyph);
/// <summary>
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
/// </summary>
@ -94,5 +67,13 @@ namespace Avalonia.Platform
/// An array of glyph advances.
/// </returns>
int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs);
/// <summary>
/// Returns the contents of the table data for the specified tag.
/// </summary>
/// <param name="tag">The table tag to get the data for.</param>
/// <param name="table">The contents of the table data for the specified tag.</param>
/// <returns>Returns <c>true</c> if the content exists, otherwise <c>false</c>.</returns>
bool TryGetTable(uint tag, out byte[] table);
}
}

16
src/Avalonia.Base/Media/TextDecoration.cs

@ -155,9 +155,9 @@ namespace Avalonia.Media
/// </summary>
/// <param name="drawingContext">The drawing context.</param>
/// <param name="glyphRun">The decorated run.</param>
/// <param name="fontMetrics">The font metrics of the decorated run.</param>
/// <param name="textMetrics">The font metrics of the decorated run.</param>
/// <param name="defaultBrush">The default brush that is used to draw the decoration.</param>
internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, FontMetrics fontMetrics, IBrush defaultBrush)
internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, TextMetrics textMetrics, IBrush defaultBrush)
{
var baselineOrigin = glyphRun.BaselineOrigin;
var thickness = StrokeThickness;
@ -168,16 +168,16 @@ namespace Avalonia.Media
switch (Location)
{
case TextDecorationLocation.Underline:
thickness = fontMetrics.UnderlineThickness;
thickness = textMetrics.UnderlineThickness;
break;
case TextDecorationLocation.Strikethrough:
thickness = fontMetrics.StrikethroughThickness;
thickness = textMetrics.StrikethroughThickness;
break;
}
break;
case TextDecorationUnit.FontRenderingEmSize:
thickness = fontMetrics.FontRenderingEmSize * thickness;
thickness = textMetrics.FontRenderingEmSize * thickness;
break;
}
@ -189,17 +189,17 @@ namespace Avalonia.Media
origin += glyphRun.BaselineOrigin;
break;
case TextDecorationLocation.Strikethrough:
origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.StrikethroughPosition);
origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.StrikethroughPosition);
break;
case TextDecorationLocation.Underline:
origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.UnderlinePosition);
origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.UnderlinePosition);
break;
}
switch (StrokeOffsetUnit)
{
case TextDecorationUnit.FontRenderingEmSize:
origin += new Point(0, StrokeOffset * fontMetrics.FontRenderingEmSize);
origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize);
break;
case TextDecorationUnit.Pixel:
origin += new Point(0, StrokeOffset);

6
src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs

@ -8,13 +8,13 @@ namespace Avalonia.Media.TextFormatting
{
private static readonly IComparer<GlyphInfo> s_clusterComparer = new CompareClusters();
public ShapedBuffer(ReadOnlySlice<char> text, int length, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
public ShapedBuffer(ReadOnlySlice<char> text, int length, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
: this(text, new GlyphInfo[length], glyphTypeface, fontRenderingEmSize, bidiLevel)
{
}
internal ShapedBuffer(ReadOnlySlice<char> text, ArraySlice<GlyphInfo> glyphInfos, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
internal ShapedBuffer(ReadOnlySlice<char> text, ArraySlice<GlyphInfo> glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
{
Text = text;
GlyphInfos = glyphInfos;
@ -29,7 +29,7 @@ namespace Avalonia.Media.TextFormatting
public int Length => GlyphInfos.Length;
public GlyphTypeface GlyphTypeface { get; }
public IGlyphTypeface GlyphTypeface { get; }
public double FontRenderingEmSize { get; }

9
src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs

@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
@ -18,7 +17,7 @@ namespace Avalonia.Media.TextFormatting
Text = shapedBuffer.Text;
Properties = properties;
TextSourceLength = Text.Length;
FontMetrics = new FontMetrics(properties.Typeface, properties.FontRenderingEmSize);
TextMetrics = new TextMetrics(properties.Typeface, properties.FontRenderingEmSize);
}
public bool IsReversed { get; private set; }
@ -36,9 +35,9 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/>
public override int TextSourceLength { get; }
public FontMetrics FontMetrics { get; }
public TextMetrics TextMetrics { get; }
public override double Baseline => -FontMetrics.Ascent;
public override double Baseline => -TextMetrics.Ascent;
public override Size Size => GlyphRun.Size;
@ -89,7 +88,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textDecoration in Properties.TextDecorations)
{
textDecoration.Draw(drawingContext, GlyphRun, FontMetrics, Properties.ForegroundBrush);
textDecoration.Draw(drawingContext, GlyphRun, TextMetrics, Properties.ForegroundBrush);
}
}
}

26
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -1378,17 +1378,17 @@ namespace Avalonia.Media.TextFormatting
private TextLineMetrics CreateLineMetrics()
{
var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface;
var fontMetrics = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface.Metrics;
var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight;
var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
var width = 0d;
var widthIncludingWhitespace = 0d;
var trailingWhitespaceLength = 0;
var newLineLength = 0;
var ascent = glyphTypeface.Ascent * scale;
var descent = glyphTypeface.Descent * scale;
var lineGap = glyphTypeface.LineGap * scale;
var ascent = fontMetrics.Ascent * scale;
var descent = fontMetrics.Descent * scale;
var lineGap = fontMetrics.LineGap * scale;
var height = descent - ascent + lineGap;
@ -1400,26 +1400,26 @@ namespace Avalonia.Media.TextFormatting
{
case ShapedTextCharacters textRun:
{
var fontMetrics =
new FontMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize);
var textMetrics =
new TextMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize);
if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize)
{
fontRenderingEmSize = textRun.Properties.FontRenderingEmSize;
if (ascent > fontMetrics.Ascent)
if (ascent > textMetrics.Ascent)
{
ascent = fontMetrics.Ascent;
ascent = textMetrics.Ascent;
}
if (descent < fontMetrics.Descent)
if (descent < textMetrics.Descent)
{
descent = fontMetrics.Descent;
descent = textMetrics.Descent;
}
if (lineGap < fontMetrics.LineGap)
if (lineGap < textMetrics.LineGap)
{
lineGap = fontMetrics.LineGap;
lineGap = textMetrics.LineGap;
}
if (descent - ascent + lineGap > height)

24
src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs → src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs

@ -1,33 +1,33 @@
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// A metric that holds information about font specific measurements.
/// A metric that holds information about text specific measurements.
/// </summary>
public readonly struct FontMetrics
public readonly struct TextMetrics
{
public FontMetrics(Typeface typeface, double fontRenderingEmSize)
public TextMetrics(Typeface typeface, double fontRenderingEmSize)
{
var glyphTypeface = typeface.GlyphTypeface;
var fontMetrics = typeface.GlyphTypeface.Metrics;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight;
var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
FontRenderingEmSize = fontRenderingEmSize;
Ascent = glyphTypeface.Ascent * scale;
Ascent = fontMetrics.Ascent * scale;
Descent = glyphTypeface.Descent * scale;
Descent = fontMetrics.Descent * scale;
LineGap = glyphTypeface.LineGap * scale;
LineGap = fontMetrics.LineGap * scale;
LineHeight = Descent - Ascent + LineGap;
UnderlineThickness = glyphTypeface.UnderlineThickness * scale;
UnderlineThickness = fontMetrics.UnderlineThickness * scale;
UnderlinePosition = glyphTypeface.UnderlinePosition * scale;
UnderlinePosition = fontMetrics.UnderlinePosition * scale;
StrikethroughThickness = glyphTypeface.StrikethroughThickness * scale;
StrikethroughThickness = fontMetrics.StrikethroughThickness * scale;
StrikethroughPosition = glyphTypeface.StrikethroughPosition * scale;
StrikethroughPosition = fontMetrics.StrikethroughPosition * scale;
}
/// <summary>

4
src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs

@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting
public readonly struct TextShaperOptions
{
public TextShaperOptions(
GlyphTypeface typeface,
IGlyphTypeface typeface,
double fontRenderingEmSize = 12,
sbyte bidiLevel = 0,
CultureInfo? culture = null,
@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting
/// <summary>
/// Get the typeface.
/// </summary>
public GlyphTypeface Typeface { get; }
public IGlyphTypeface Typeface { get; }
/// <summary>
/// Get the font rendering em size.
/// </summary>

5
src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using System;
using System.Runtime.CompilerServices;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
@ -165,7 +166,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// <param name="index">The index to read at.</param>
/// <param name="count">The count of character that were read.</param>
/// <returns></returns>
public static Codepoint ReadAt(ReadOnlySlice<char> text, int index, out int count)
public static Codepoint ReadAt(ReadOnlySpan<char> text, int index, out int count)
{
count = 1;

2
src/Avalonia.Base/Media/Typeface.cs

@ -81,7 +81,7 @@ namespace Avalonia.Media
/// <value>
/// The glyph typeface.
/// </value>
public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
public IGlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
public static bool operator !=(Typeface a, Typeface b)
{

2
src/Avalonia.Base/Platform/IFontManagerImpl.cs

@ -43,6 +43,6 @@ namespace Avalonia.Platform
/// <returns>0
/// The created glyph typeface. Can be <c>Null</c> if it was not possible to create a glyph typeface.
/// </returns>
IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface);
IGlyphTypeface CreateGlyphTypeface(Typeface typeface);
}
}

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

@ -180,7 +180,7 @@ namespace Avalonia.Platform
/// <remarks>
/// This buffer only holds glyph indices.
/// </remarks>
IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a horizontal platform glyph run buffer.
@ -192,7 +192,7 @@ namespace Avalonia.Platform
/// <remarks>
/// This buffer holds glyph indices and glyph advances.
/// </remarks>
IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a positioned platform glyph run buffer.
@ -204,7 +204,7 @@ namespace Avalonia.Platform
/// <remarks>
/// This buffer holds glyph indices, glyph advances and glyph positions.
/// </remarks>
IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Gets a value indicating whether the platform directly supports rectangles with rounded corners.

2
src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs

@ -25,7 +25,7 @@ internal class FpsCounter
// ASCII chars
private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
public FpsCounter(GlyphTypeface typeface)
public FpsCounter(IGlyphTypeface typeface)
{
for (var c = FirstChar; c <= LastChar; c++)
{

2
src/Avalonia.Base/Utilities/ReadOnlySlice.cs

@ -214,6 +214,8 @@ namespace Avalonia.Utilities
return new ReadOnlySlice<T>(memory);
}
public static implicit operator ReadOnlySpan<T>(ReadOnlySlice<T> slice) => slice.Span;
internal class ReadOnlySliceDebugView
{
private readonly ReadOnlySlice<T> _readOnlySlice;

22
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -75,8 +75,13 @@ namespace Avalonia.Headless
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
class HeadlessGlyphTypefaceImpl : IGlyphTypefaceImpl
class HeadlessGlyphTypefaceImpl : IGlyphTypeface
{
public FontMetrics Metrics => new FontMetrics
{
};
public short DesignEmHeight => 10;
public int Ascent => 5;
@ -106,6 +111,13 @@ namespace Avalonia.Headless
return 1;
}
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = 1;
return true;
}
public int GetGlyphAdvance(ushort glyph)
{
return 1;
@ -120,6 +132,12 @@ namespace Avalonia.Headless
{
return codepoints.ToArray().Select(x => (ushort)x).ToArray();
}
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
return false;
}
}
class HeadlessTextShaperStub : ITextShaperImpl
@ -136,7 +154,7 @@ namespace Avalonia.Headless
class HeadlessFontManagerStub : IFontManagerImpl
{
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
return new HeadlessGlyphTypefaceImpl();
}

2
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -102,7 +102,7 @@ namespace Avalonia.Skia
return false;
}
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
SKTypeface skTypeface = null;

92
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@ -1,14 +1,14 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Platform;
using HarfBuzzSharp;
using SkiaSharp;
namespace Avalonia.Skia
{
[Unstable]
public class GlyphTypefaceImpl : IGlyphTypefaceImpl
public class GlyphTypefaceImpl : IGlyphTypeface
{
private bool _isDisposed;
@ -25,35 +25,30 @@ namespace Avalonia.Skia
Font.SetFunctionsOpenType();
DesignEmHeight = (short)Typeface.UnitsPerEm;
var metrics = Typeface.ToFont().Metrics;
const double defaultFontRenderingEmSize = 12.0;
Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm);
Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm);
LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm);
UnderlinePosition = metrics.UnderlinePosition != null ?
Metrics = new FontMetrics
{
DesignEmHeight = (short)Typeface.UnitsPerEm,
Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm),
Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm),
LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm),
UnderlinePosition = metrics.UnderlinePosition != null ?
(int)(metrics.UnderlinePosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0;
UnderlineThickness = metrics.UnderlineThickness != null ?
0,
UnderlineThickness = metrics.UnderlineThickness != null ?
(int)(metrics.UnderlineThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0;
StrikethroughPosition = metrics.StrikeoutPosition != null ?
0,
StrikethroughPosition = metrics.StrikeoutPosition != null ?
(int)(metrics.StrikeoutPosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0;
StrikethroughThickness = metrics.StrikeoutThickness != null ?
0,
StrikethroughThickness = metrics.StrikeoutThickness != null ?
(int)(metrics.StrikeoutThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0;
IsFixedPitch = Typeface.IsFixedPitch;
0,
IsFixedPitch = Typeface.IsFixedPitch
};
GlyphCount = Typeface.GlyphCount;
@ -69,41 +64,16 @@ namespace Avalonia.Skia
public SKTypeface Typeface { get; }
public int ReplacementCodepoint { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public short DesignEmHeight { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Ascent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Descent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int LineGap { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlinePosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlineThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughPosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool IsFixedPitch { get; }
public FontMetrics Metrics { get; }
public int GlyphCount { get; }
public bool IsFakeBold { get; }
public bool IsFakeItalic { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint)
{
if (Font.TryGetGlyph(codepoint, out var glyph))
@ -114,7 +84,14 @@ namespace Avalonia.Skia
return 0;
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = GetGlyph(codepoint);
return glyph != 0;
}
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
{
var glyphs = new ushort[codepoints.Length];
@ -130,13 +107,13 @@ namespace Avalonia.Skia
return glyphs;
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
/// <inheritdoc cref="IGlyphTypeface"/>
public int GetGlyphAdvance(ushort glyph)
{
return Font.GetHorizontalGlyphAdvance(glyph);
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
/// <inheritdoc cref="IGlyphTypeface"/>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{
var glyphIndices = new uint[glyphs.Length];
@ -184,5 +161,10 @@ namespace Avalonia.Skia
Dispose(true);
GC.SuppressFinalize(this);
}
public bool TryGetTable(uint tag, out byte[] table)
{
return Typeface.TryGetTableData(tag, out table);
}
}
}

18
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -70,7 +70,7 @@ namespace Avalonia.Skia
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface)
{
throw new InvalidOperationException("PlatformImpl can't be null.");
}
@ -244,13 +244,13 @@ namespace Avalonia.Skia
"Current GPU acceleration backend does not support OpenGL integration");
}
public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
=> new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
=> new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
=> new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer
@ -258,11 +258,11 @@ namespace Avalonia.Skia
protected readonly SKTextBlobBuilder _builder;
protected readonly SKFont _font;
public SKGlyphRunBufferBase(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
public SKGlyphRunBufferBase(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
_builder = new SKTextBlobBuilder();
var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl;
var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
_font = new SKFont
{
@ -289,7 +289,7 @@ namespace Avalonia.Skia
{
private readonly SKRunBuffer _buffer;
public SKGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
public SKGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
{
_buffer = _builder.AllocateRun(_font, length, 0, 0);
}
@ -301,7 +301,7 @@ namespace Avalonia.Skia
{
private readonly SKHorizontalRunBuffer _buffer;
public SKHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
public SKHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
{
_buffer = _builder.AllocateHorizontalRun(_font, length, 0);
}
@ -315,7 +315,7 @@ namespace Avalonia.Skia
{
private readonly SKPositionedRunBuffer _buffer;
public SKPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
public SKPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
{
_buffer = _builder.AllocatePositionedRun(_font, length);
}

2
src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs

@ -62,7 +62,7 @@ namespace Avalonia.Direct2D1.Media
return false;
}
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
return new GlyphTypefaceImpl(typeface);
}

105
src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs

@ -1,14 +1,15 @@
using System;
using System.Drawing.Drawing2D;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Platform;
using HarfBuzzSharp;
using SharpDX.DirectWrite;
using FontMetrics = Avalonia.Media.FontMetrics;
namespace Avalonia.Direct2D1.Media
{
[Unstable]
public class GlyphTypefaceImpl : IGlyphTypefaceImpl
public class GlyphTypefaceImpl : IGlyphTypeface
{
private bool _isDisposed;
@ -26,40 +27,28 @@ namespace Avalonia.Direct2D1.Media
Font.GetScale(out var xScale, out _);
DesignEmHeight = (short)xScale;
if (!Font.TryGetHorizontalFontExtents(out var fontExtents))
{
Font.TryGetVerticalFontExtents(out fontExtents);
}
Ascent = -fontExtents.Ascender;
Descent = -fontExtents.Descender;
LineGap = fontExtents.LineGap;
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition))
{
UnderlinePosition = underlinePosition;
}
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness))
{
UnderlineThickness = underlineThickness;
}
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness);
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition))
Metrics = new FontMetrics
{
StrikethroughPosition = strikethroughPosition;
}
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness))
{
StrikethroughThickness = strikethroughThickness;
}
IsFixedPitch = FontFace.IsMonospacedFont;
DesignEmHeight = (short)xScale,
Ascent = -fontExtents.Ascender,
Descent = -fontExtents.Descender,
LineGap = fontExtents.LineGap,
UnderlinePosition = underlinePosition,
UnderlineThickness = underlineThickness,
StrikethroughPosition = strikethroughPosition,
StrikethroughThickness = strikethroughThickness,
IsFixedPitch = FontFace.IsMonospacedFont
};
}
private Blob GetTable(Face face, Tag tag)
@ -89,37 +78,11 @@ namespace Avalonia.Direct2D1.Media
public HarfBuzzSharp.Font Font { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public short DesignEmHeight { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Ascent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Descent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int LineGap { get; }
//ToDo: Read font table for these values
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlinePosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlineThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughPosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool IsFixedPitch { get; }
public FontMetrics Metrics { get; }
public int GlyphCount { get; set; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint)
{
if (Font.TryGetGlyph(codepoint, out var glyph))
@ -130,7 +93,14 @@ namespace Avalonia.Direct2D1.Media
return 0;
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = GetGlyph(codepoint);
return glyph != 0;
}
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
{
var glyphs = new ushort[codepoints.Length];
@ -146,13 +116,13 @@ namespace Avalonia.Direct2D1.Media
return glyphs;
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
/// <inheritdoc cref="IGlyphTypeface"/>
public int GetGlyphAdvance(ushort glyph)
{
return Font.GetHorizontalGlyphAdvance(glyph);
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
/// <inheritdoc cref="IGlyphTypeface"/>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{
var glyphIndices = new uint[glyphs.Length];
@ -189,6 +159,21 @@ namespace Avalonia.Direct2D1.Media
Dispose(true);
GC.SuppressFinalize(this);
}
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
var blob = Face.ReferenceTable(tag);
if (blob.Length > 0)
{
table = blob.AsSpan().ToArray();
return true;
}
return false;
}
}
}

2
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@ -64,7 +64,7 @@ namespace Avalonia.Skia.UnitTests.Media
return true;
}
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
SKTypeface skTypeface;

2
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -237,7 +237,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var glyph = typeface.GlyphTypeface.GetGlyph('a');
var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) *
(12.0 / typeface.GlyphTypeface.DesignEmHeight);
(12.0 / typeface.GlyphTypeface.Metrics.DesignEmHeight);
var paragraphWidth = advance * numberOfCharactersPerLine;

4
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -578,9 +578,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
var glyphTypeface = Typeface.Default.GlyphTypeface;
var emHeight = glyphTypeface.DesignEmHeight;
var emHeight = glyphTypeface.Metrics.DesignEmHeight;
var lineHeight = (glyphTypeface.Descent - glyphTypeface.Ascent) * (12.0 / emHeight);
var lineHeight = glyphTypeface.Metrics.LineSpacing * (12.0 / emHeight);
var layout = new TextLayout(
text,

2
tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs

@ -58,7 +58,7 @@ namespace Avalonia.UnitTests
return false;
}
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
var fontFamily = typeface.FontFamily;

96
tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs

@ -1,11 +1,11 @@
using System;
using System.IO;
using Avalonia.Platform;
using Avalonia.Media;
using HarfBuzzSharp;
namespace Avalonia.UnitTests
{
public class HarfBuzzGlyphTypefaceImpl : IGlyphTypefaceImpl
public class HarfBuzzGlyphTypefaceImpl : IGlyphTypeface
{
private bool _isDisposed;
private Blob _blob;
@ -21,28 +21,28 @@ namespace Avalonia.UnitTests
Font.SetFunctionsOpenType();
Font.GetScale(out var scale, out _);
DesignEmHeight = (short)scale;
var metrics = Font.OpenTypeMetrics;
const double defaultFontRenderingEmSize = 12.0;
Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * DesignEmHeight);
Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * DesignEmHeight);
var metrics = Font.OpenTypeMetrics;
LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * DesignEmHeight);
Metrics = new FontMetrics
{
DesignEmHeight = (short)scale,
Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * scale),
Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * scale),
LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * scale),
UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * DesignEmHeight);
UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * scale),
UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * DesignEmHeight);
UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * scale),
StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * DesignEmHeight);
StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * scale),
StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * DesignEmHeight);
StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * scale),
IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b'));
IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b'))
};
GlyphCount = Face.GlyphCount;
@ -51,44 +51,19 @@ namespace Avalonia.UnitTests
IsFakeItalic = isFakeItalic;
}
public FontMetrics Metrics { get; }
public Face Face { get; }
public Font Font { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public short DesignEmHeight { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Ascent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Descent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int LineGap { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlinePosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlineThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughPosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool IsFixedPitch { get; }
public int GlyphCount { get; set; }
public bool IsFakeBold { get; }
public bool IsFakeItalic { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint)
{
if (Font.TryGetGlyph(codepoint, out var glyph))
@ -99,7 +74,21 @@ namespace Avalonia.UnitTests
return 0;
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool TryGetGlyph(uint codepoint,out ushort glyph)
{
glyph = 0;
if (Font.TryGetGlyph(codepoint, out var glyphId))
{
glyph = (ushort)glyphId;
return true;
}
return false;
}
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
{
var glyphs = new ushort[codepoints.Length];
@ -115,13 +104,13 @@ namespace Avalonia.UnitTests
return glyphs;
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
/// <inheritdoc cref="IGlyphTypeface"/>
public int GetGlyphAdvance(ushort glyph)
{
return Font.GetHorizontalGlyphAdvance(glyph);
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
/// <inheritdoc cref="IGlyphTypeface"/>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{
var glyphIndices = new uint[glyphs.Length];
@ -134,6 +123,21 @@ namespace Avalonia.UnitTests
return Font.GetHorizontalGlyphAdvances(glyphIndices);
}
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
var blob = Face.ReferenceTable(tag);
if (blob.Length > 0)
{
table = blob.AsSpan().ToArray();
return true;
}
return false;
}
private void Dispose(bool disposing)
{
if (_isDisposed)

2
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

@ -33,7 +33,7 @@ namespace Avalonia.UnitTests
return false;
}
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
return new MockGlyphTypeface();
}

34
tests/Avalonia.UnitTests/MockGlyphTypeface.cs

@ -1,19 +1,18 @@
using System;
using Avalonia.Platform;
using Avalonia.Media;
namespace Avalonia.UnitTests
{
public class MockGlyphTypeface : IGlyphTypefaceImpl
public class MockGlyphTypeface : IGlyphTypeface
{
public short DesignEmHeight => 10;
public int Ascent => 2;
public int Descent => 10;
public int LineGap { get; }
public int UnderlinePosition { get; }
public int UnderlineThickness { get; }
public int StrikethroughPosition { get; }
public int StrikethroughThickness { get; }
public bool IsFixedPitch { get; }
public FontMetrics Metrics => new FontMetrics
{
DesignEmHeight = 10,
Ascent = 2,
Descent = 10,
IsFixedPitch = true
};
public int GlyphCount => 1337;
public ushort GetGlyph(uint codepoint)
@ -31,6 +30,13 @@ namespace Avalonia.UnitTests
return 8;
}
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = 8;
return true;
}
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{
var advances = new int[glyphs.Length];
@ -44,5 +50,11 @@ namespace Avalonia.UnitTests
}
public void Dispose() { }
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
return false;
}
}
}

Loading…
Cancel
Save