Browse Source

Merge branch 'master' into 8970-datagrid-add-multiple-fix

pull/9135/head
Max Katz 4 years ago
committed by GitHub
parent
commit
01dd88ef5b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .editorconfig
  2. 13
      dirs.proj
  3. 24
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  4. 4
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  5. 5
      src/Avalonia.Base/Avalonia.Base.csproj
  6. 12
      src/Avalonia.Base/Media/FontManager.cs
  7. 58
      src/Avalonia.Base/Media/FontMetrics.cs
  8. 95
      src/Avalonia.Base/Media/GlyphRun.cs
  9. 125
      src/Avalonia.Base/Media/GlyphTypeface.cs
  10. 68
      src/Avalonia.Base/Media/IGlyphTypeface.cs
  11. 16
      src/Avalonia.Base/Media/TextDecoration.cs
  12. 6
      src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
  13. 9
      src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs
  14. 26
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  15. 24
      src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs
  16. 4
      src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
  17. 5
      src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
  18. 2
      src/Avalonia.Base/Media/Typeface.cs
  19. 2
      src/Avalonia.Base/Platform/IFontManagerImpl.cs
  20. 22
      src/Avalonia.Base/Platform/IGlyphRunBuffer.cs
  21. 37
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  22. 2
      src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
  23. 2
      src/Avalonia.Base/Utilities/ReadOnlySlice.cs
  24. 30
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  25. 30
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  26. 38
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  27. 24
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  28. 7
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  29. 7
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  30. 13
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  31. 30
      src/Avalonia.Themes.Fluent/Controls/Slider.xaml
  32. 20
      src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
  33. 13
      src/Avalonia.Themes.Simple/Controls/DatePicker.xaml
  34. 19
      src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
  35. 2
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  36. 90
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  37. 184
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  38. 2
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  39. 91
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  40. 2
      src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
  41. 105
      src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
  42. 2
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
  43. 2
      tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
  44. 15
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  45. 16
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  46. 2
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  47. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  48. 4
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  49. 2
      tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
  50. 96
      tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
  51. 2
      tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
  52. 2
      tests/Avalonia.UnitTests/MockFontManagerImpl.cs
  53. 35
      tests/Avalonia.UnitTests/MockGlyphTypeface.cs
  54. 15
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

2
.editorconfig

@ -141,6 +141,8 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme
dotnet_diagnostic.CA1802.severity = warning dotnet_diagnostic.CA1802.severity = warning
# CA1825: Avoid zero-length array allocations # CA1825: Avoid zero-length array allocations
dotnet_diagnostic.CA1825.severity = warning dotnet_diagnostic.CA1825.severity = warning
# CA1821: Remove empty finalizers
dotnet_diagnostic.CA1821.severity = warning
# Wrapping preferences # Wrapping preferences
csharp_wrap_before_ternary_opsigns = false csharp_wrap_before_ternary_opsigns = false

13
dirs.proj

@ -9,21 +9,18 @@
<ProjectReference Remove="**/*.shproj" /> <ProjectReference Remove="**/*.shproj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" /> <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/**/*.*proj" /> <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/**/*.*proj" />
<ProjectReference Remove="tests/Avalonia.ReactiveUI.Events.UnitTests/Avalonia.ReactiveUI.Events.UnitTests.csproj" /> <!-- Exclude iOS, Android and Web samples from build -->
<ProjectReference Remove="samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj" /> <ProjectReference Remove="samples/*.iOS/*.csproj" />
<ProjectReference Remove="samples/MobileSandbox.iOS/MobileSandbox.iOS.csproj" /> <ProjectReference Remove="samples/*.Android/*.csproj" />
<ProjectReference Remove="samples/ControlCatalog.iOS.Legacy/ControlCatalog.iOS.Legacy.csproj" /> <ProjectReference Remove="samples/*.Web/*.csproj" />
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
<ProjectReference Remove="samples/MobileSandbox.Android/MobileSandbox.Android.csproj" />
<ProjectReference Remove="src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows')) OR '$(MSBuildRuntimeType)' != 'Full'"> <ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows')) OR '$(MSBuildRuntimeType)' != 'Full'">
<ProjectReference Remove="src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj" /> <ProjectReference Remove="src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj" />
<ProjectReference Remove="samples/interop/**/*.*proj" /> <ProjectReference Remove="samples/interop/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.Desktop/*.*proj" /> <ProjectReference Remove="samples/ControlCatalog.Desktop/*.*proj" />
</ItemGroup> </ItemGroup>
<!-- Build android and iOS projects only on Windows, where we have installed android workload -->
<!-- Build android and iOS projects only on Windows, where we have installed android workload -->
<ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows'))"> <ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows'))">
<ProjectReference Remove="src/Android/**/*.*proj" /> <ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="src/iOS/**/*.*proj" /> <ProjectReference Remove="src/iOS/**/*.*proj" />

24
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@ -13,17 +13,17 @@
Margin="16" Margin="16"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Spacing="16"> Spacing="16">
<TextBlock FontSize="18">A simple DatePicker with a header</TextBlock> <TextBlock FontSize="18">A simple DatePicker</TextBlock>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<DatePicker Header="Pick a date" /> <DatePicker />
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;DatePicker Header="Pick a date" /&gt; &lt;DatePicker/&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
@ -33,7 +33,7 @@
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<DatePicker Header="Pick a date"> <DatePicker >
<DataValidationErrors.Error> <DataValidationErrors.Error>
<sys:Exception /> <sys:Exception />
</DataValidationErrors.Error> </DataValidationErrors.Error>
@ -79,24 +79,24 @@
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker Header="Pick a time"> <TimePicker>
<DataValidationErrors.Error> <DataValidationErrors.Error>
<sys:Exception /> <sys:Exception />
</DataValidationErrors.Error> </DataValidationErrors.Error>
</TimePicker> </TimePicker>
</Border> </Border>
<TextBlock FontSize="18">A TimePicker with a header and minute increments specified.</TextBlock> <TextBlock FontSize="18">A TimePicker with minute increments specified.</TextBlock>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker Header="Arrival time" MinuteIncrement="15" /> <TimePicker MinuteIncrement="15" />
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;TimePicker Header="Arrival time" MinuteIncrement="15" /&gt; &lt;TimePicker MinuteIncrement="15" /&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
@ -107,13 +107,13 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /> <TimePicker ClockIdentifier="12HourClock"/>
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /&gt; &lt;TimePicker ClockIdentifier="12HourClock" /&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
@ -124,13 +124,13 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /> <TimePicker ClockIdentifier="24HourClock" />
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /&gt; &lt;TimePicker ClockIdentifier="24HourClock" /&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>

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

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

5
src/Avalonia.Base/Avalonia.Base.csproj

@ -21,6 +21,9 @@
<Import Project="..\..\build\NullableEnable.props" /> <Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" /> <Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" /> <Import Project="..\..\build\SourceGenerators.props" />
<ItemGroup>
<Compile Include="..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo"> <ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
@ -38,7 +41,7 @@
<InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)"/> <InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" /> <InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup> </ItemGroup>

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

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

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

@ -0,0 +1,58 @@
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; }
}
}

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

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -15,7 +16,7 @@ namespace Avalonia.Media
private static readonly IComparer<int> s_descendingComparer = new ReverseComparer<int>(); private static readonly IComparer<int> s_descendingComparer = new ReverseComparer<int>();
private IGlyphRunImpl? _glyphRunImpl; private IGlyphRunImpl? _glyphRunImpl;
private GlyphTypeface _glyphTypeface; private IGlyphTypeface _glyphTypeface;
private double _fontRenderingEmSize; private double _fontRenderingEmSize;
private int _biDiLevel; private int _biDiLevel;
private Point? _baselineOrigin; private Point? _baselineOrigin;
@ -42,7 +43,7 @@ namespace Avalonia.Media
/// <param name="glyphClusters">The glyph clusters.</param> /// <param name="glyphClusters">The glyph clusters.</param>
/// <param name="biDiLevel">The bidi level.</param> /// <param name="biDiLevel">The bidi level.</param>
public GlyphRun( public GlyphRun(
GlyphTypeface glyphTypeface, IGlyphTypeface glyphTypeface,
double fontRenderingEmSize, double fontRenderingEmSize,
ReadOnlySlice<char> characters, ReadOnlySlice<char> characters,
IReadOnlyList<ushort> glyphIndices, IReadOnlyList<ushort> glyphIndices,
@ -69,9 +70,9 @@ namespace Avalonia.Media
} }
/// <summary> /// <summary>
/// Gets the <see cref="Media.GlyphTypeface"/> for the <see cref="GlyphRun"/>. /// Gets the <see cref="IGlyphTypeface"/> for the <see cref="GlyphRun"/>.
/// </summary> /// </summary>
public GlyphTypeface GlyphTypeface => _glyphTypeface; public IGlyphTypeface GlyphTypeface => _glyphTypeface;
/// <summary> /// <summary>
/// Gets or sets the em size used for rendering the <see cref="GlyphRun"/>. /// Gets or sets the em size used for rendering the <see cref="GlyphRun"/>.
@ -171,7 +172,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets the scale of the current <see cref="Media.GlyphTypeface"/> /// Gets the scale of the current <see cref="Media.GlyphTypeface"/>
/// </summary> /// </summary>
internal double Scale => FontRenderingEmSize / GlyphTypeface.DesignEmHeight; internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight;
/// <summary> /// <summary>
/// Returns <c>true</c> if the text direction is left-to-right. Otherwise, returns <c>false</c>. /// Returns <c>true</c> if the text direction is left-to-right. Otherwise, returns <c>false</c>.
@ -612,7 +613,7 @@ namespace Avalonia.Media
/// <returns>The baseline origin.</returns> /// <returns>The baseline origin.</returns>
private Point CalculateBaselineOrigin() private Point CalculateBaselineOrigin()
{ {
return new Point(0, -GlyphTypeface.Ascent * Scale); return new Point(0, -GlyphTypeface.Metrics.Ascent * Scale);
} }
private GlyphRunMetrics CreateGlyphRunMetrics() private GlyphRunMetrics CreateGlyphRunMetrics()
@ -636,7 +637,7 @@ namespace Avalonia.Media
} }
var isReversed = firstCluster > lastCluster; var isReversed = firstCluster > lastCluster;
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; var height = GlyphTypeface.Metrics.LineSpacing * Scale;
var widthIncludingTrailingWhitespace = 0d; var widthIncludingTrailingWhitespace = 0d;
var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount); var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount);
@ -854,9 +855,87 @@ namespace Avalonia.Media
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
_glyphRunImpl = CreateGlyphRunImpl();
}
private IGlyphRunImpl CreateGlyphRunImpl()
{
IGlyphRunImpl glyphRunImpl;
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>(); var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var count = GlyphIndices.Count;
var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight);
if (GlyphOffsets == null)
{
if (GlyphTypeface.Metrics.IsFixedPitch)
{
var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
for (int i = 0; i < glyphs.Length; i++)
{
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
else
{
var buffer = platformRenderInterface.AllocateHorizontalGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
var positions = buffer.GlyphPositions;
var width = 0d;
for (var i = 0; i < count; i++)
{
positions[i] = (float)width;
if (GlyphAdvances == null)
{
width += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
}
else
{
width += GlyphAdvances[i];
}
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
}
else
{
var buffer = platformRenderInterface.AllocatePositionedGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
var glyphPositions = buffer.GlyphPositions;
var currentX = 0.0;
for (var i = 0; i < count; i++)
{
var glyphOffset = GlyphOffsets[i];
glyphPositions[i] = new PointF((float)(currentX + glyphOffset.X), (float)glyphOffset.Y);
if (GlyphAdvances == null)
{
currentX += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
}
else
{
currentX += GlyphAdvances[i];
}
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this); return glyphRunImpl;
} }
void IDisposable.Dispose() void IDisposable.Dispose()

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

@ -1,125 +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>
/// 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();
}
}
}

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

@ -1,55 +1,23 @@
using System; using System;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Platform namespace Avalonia.Media
{ {
[Unstable] [Unstable]
public interface IGlyphTypefaceImpl : IDisposable public interface IGlyphTypeface : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the font design units per em. /// Gets the number of glyphs held by this glyph typeface.
/// </summary> /// </summary>
short DesignEmHeight { get; } int GlyphCount { get; }
/// <summary> /// <summary>
/// Gets the recommended distance above the baseline in design em size. /// Gets the font metrics.
/// </summary> /// </summary>
int Ascent { get; } /// <returns>
/// The font metrics.
/// <summary> /// </returns>
/// Gets the recommended distance under the baseline in design em size. FontMetrics Metrics { get; }
/// </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.
/// </summary>
bool IsFixedPitch { get; }
/// <summary> /// <summary>
/// Returns an glyph index for the specified codepoint. /// Returns an glyph index for the specified codepoint.
@ -63,6 +31,16 @@ namespace Avalonia.Platform
/// </returns> /// </returns>
ushort GetGlyph(uint codepoint); 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> /// <summary>
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>. /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
/// </summary> /// </summary>
@ -89,5 +67,13 @@ namespace Avalonia.Platform
/// An array of glyph advances. /// An array of glyph advances.
/// </returns> /// </returns>
int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs); 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> /// </summary>
/// <param name="drawingContext">The drawing context.</param> /// <param name="drawingContext">The drawing context.</param>
/// <param name="glyphRun">The decorated run.</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> /// <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 baselineOrigin = glyphRun.BaselineOrigin;
var thickness = StrokeThickness; var thickness = StrokeThickness;
@ -168,16 +168,16 @@ namespace Avalonia.Media
switch (Location) switch (Location)
{ {
case TextDecorationLocation.Underline: case TextDecorationLocation.Underline:
thickness = fontMetrics.UnderlineThickness; thickness = textMetrics.UnderlineThickness;
break; break;
case TextDecorationLocation.Strikethrough: case TextDecorationLocation.Strikethrough:
thickness = fontMetrics.StrikethroughThickness; thickness = textMetrics.StrikethroughThickness;
break; break;
} }
break; break;
case TextDecorationUnit.FontRenderingEmSize: case TextDecorationUnit.FontRenderingEmSize:
thickness = fontMetrics.FontRenderingEmSize * thickness; thickness = textMetrics.FontRenderingEmSize * thickness;
break; break;
} }
@ -189,17 +189,17 @@ namespace Avalonia.Media
origin += glyphRun.BaselineOrigin; origin += glyphRun.BaselineOrigin;
break; break;
case TextDecorationLocation.Strikethrough: case TextDecorationLocation.Strikethrough:
origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.StrikethroughPosition); origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.StrikethroughPosition);
break; break;
case TextDecorationLocation.Underline: case TextDecorationLocation.Underline:
origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.UnderlinePosition); origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.UnderlinePosition);
break; break;
} }
switch (StrokeOffsetUnit) switch (StrokeOffsetUnit)
{ {
case TextDecorationUnit.FontRenderingEmSize: case TextDecorationUnit.FontRenderingEmSize:
origin += new Point(0, StrokeOffset * fontMetrics.FontRenderingEmSize); origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize);
break; break;
case TextDecorationUnit.Pixel: case TextDecorationUnit.Pixel:
origin += new Point(0, StrokeOffset); 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(); 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) : 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; Text = text;
GlyphInfos = glyphInfos; GlyphInfos = glyphInfos;
@ -29,7 +29,7 @@ namespace Avalonia.Media.TextFormatting
public int Length => GlyphInfos.Length; public int Length => GlyphInfos.Length;
public GlyphTypeface GlyphTypeface { get; } public IGlyphTypeface GlyphTypeface { get; }
public double FontRenderingEmSize { get; } public double FontRenderingEmSize { get; }

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

@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -18,7 +17,7 @@ namespace Avalonia.Media.TextFormatting
Text = shapedBuffer.Text; Text = shapedBuffer.Text;
Properties = properties; Properties = properties;
TextSourceLength = Text.Length; TextSourceLength = Text.Length;
FontMetrics = new FontMetrics(properties.Typeface, properties.FontRenderingEmSize); TextMetrics = new TextMetrics(properties.Typeface, properties.FontRenderingEmSize);
} }
public bool IsReversed { get; private set; } public bool IsReversed { get; private set; }
@ -36,9 +35,9 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override int TextSourceLength { get; } 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; public override Size Size => GlyphRun.Size;
@ -89,7 +88,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textDecoration in Properties.TextDecorations) 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() private TextLineMetrics CreateLineMetrics()
{ {
var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface; var fontMetrics = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface.Metrics;
var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
var width = 0d; var width = 0d;
var widthIncludingWhitespace = 0d; var widthIncludingWhitespace = 0d;
var trailingWhitespaceLength = 0; var trailingWhitespaceLength = 0;
var newLineLength = 0; var newLineLength = 0;
var ascent = glyphTypeface.Ascent * scale; var ascent = fontMetrics.Ascent * scale;
var descent = glyphTypeface.Descent * scale; var descent = fontMetrics.Descent * scale;
var lineGap = glyphTypeface.LineGap * scale; var lineGap = fontMetrics.LineGap * scale;
var height = descent - ascent + lineGap; var height = descent - ascent + lineGap;
@ -1400,26 +1400,26 @@ namespace Avalonia.Media.TextFormatting
{ {
case ShapedTextCharacters textRun: case ShapedTextCharacters textRun:
{ {
var fontMetrics = var textMetrics =
new FontMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize); new TextMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize);
if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize) if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize)
{ {
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) 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 namespace Avalonia.Media.TextFormatting
{ {
/// <summary> /// <summary>
/// A metric that holds information about font specific measurements. /// A metric that holds information about text specific measurements.
/// </summary> /// </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; 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; 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> /// <summary>

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

@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting
public readonly struct TextShaperOptions public readonly struct TextShaperOptions
{ {
public TextShaperOptions( public TextShaperOptions(
GlyphTypeface typeface, IGlyphTypeface typeface,
double fontRenderingEmSize = 12, double fontRenderingEmSize = 12,
sbyte bidiLevel = 0, sbyte bidiLevel = 0,
CultureInfo? culture = null, CultureInfo? culture = null,
@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting
/// <summary> /// <summary>
/// Get the typeface. /// Get the typeface.
/// </summary> /// </summary>
public GlyphTypeface Typeface { get; } public IGlyphTypeface Typeface { get; }
/// <summary> /// <summary>
/// Get the font rendering em size. /// Get the font rendering em size.
/// </summary> /// </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; using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode 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="index">The index to read at.</param>
/// <param name="count">The count of character that were read.</param> /// <param name="count">The count of character that were read.</param>
/// <returns></returns> /// <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; count = 1;

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

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

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

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

22
src/Avalonia.Base/Platform/IGlyphRunBuffer.cs

@ -0,0 +1,22 @@
using System;
using System.Drawing;
namespace Avalonia.Platform
{
public interface IGlyphRunBuffer
{
Span<ushort> GlyphIndices { get; }
IGlyphRunImpl Build();
}
public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer
{
Span<float> GlyphPositions { get; }
}
public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer
{
Span<PointF> GlyphPositions { get; }
}
}

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

@ -171,11 +171,40 @@ namespace Avalonia.Platform
IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride); IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride);
/// <summary> /// <summary>
/// Creates a platform implementation of a glyph run. /// Allocates a platform glyph run buffer.
/// </summary> /// </summary>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphTypeface">The glyph typeface.</param>
/// <returns></returns> /// <param name="fontRenderingEmSize">The font rendering em size.</param>
IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun); /// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer only holds glyph indices.
/// </remarks>
IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a horizontal platform glyph run buffer.
/// </summary>
/// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer holds glyph indices and glyph advances.
/// </remarks>
IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a positioned platform glyph run buffer.
/// </summary>
/// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer holds glyph indices, glyph advances and glyph positions.
/// </remarks>
IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary> /// <summary>
/// Gets a value indicating whether the platform directly supports rectangles with rounded corners. /// 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 // ASCII chars
private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1]; private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
public FpsCounter(GlyphTypeface typeface) public FpsCounter(IGlyphTypeface typeface)
{ {
for (var c = FirstChar; c <= LastChar; c++) 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); return new ReadOnlySlice<T>(memory);
} }
public static implicit operator ReadOnlySpan<T>(ReadOnlySlice<T> slice) => slice.Span;
internal class ReadOnlySliceDebugView internal class ReadOnlySliceDebugView
{ {
private readonly ReadOnlySlice<T> _readOnlySlice; private readonly ReadOnlySlice<T> _readOnlySlice;

30
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -40,18 +40,6 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible), AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible),
x => x.DayVisible, (x, v) => x.DayVisible = v); x => x.DayVisible, (x, v) => x.DayVisible = v);
/// <summary>
/// Defines the <see cref="Header"/> Property
/// </summary>
public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<DatePicker, object>(nameof(Header));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> Property
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate));
/// <summary> /// <summary>
/// Defines the <see cref="MaxYear"/> Property /// Defines the <see cref="MaxYear"/> Property
/// </summary> /// </summary>
@ -152,24 +140,6 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Gets or sets the DatePicker header
/// </summary>
public object Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
/// <summary>
/// Gets or sets the header template
/// </summary>
public IDataTemplate HeaderTemplate
{
get => GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the maximum year for the picker /// Gets or sets the maximum year for the picker
/// </summary> /// </summary>

30
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -34,18 +34,6 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement), AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement),
x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v); x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v);
/// <summary>
/// Defines the <see cref="Header"/> property
/// </summary>
public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<TimePicker, object>(nameof(Header));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<TimePicker, IDataTemplate>(nameof(HeaderTemplate));
/// <summary> /// <summary>
/// Defines the <see cref="ClockIdentifier"/> property /// Defines the <see cref="ClockIdentifier"/> property
/// </summary> /// </summary>
@ -103,24 +91,6 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Gets or sets the header
/// </summary>
public object Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
/// <summary>
/// Gets or sets the header template
/// </summary>
public IDataTemplate HeaderTemplate
{
get => GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the clock identifier, either 12HourClock or 24HourClock /// Gets or sets the clock identifier, either 12HourClock or 24HourClock
/// </summary> /// </summary>

38
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -110,14 +110,24 @@ namespace Avalonia.Headless
return new HeadlessBitmapStub(destinationSize, new Vector(96, 96)); return new HeadlessBitmapStub(destinationSize, new Vector(96, 96));
} }
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{ {
return new HeadlessGlyphRunStub(); return new HeadlessGeometryStub(new Rect(glyphRun.Size));
} }
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
return new HeadlessGeometryStub(new Rect(glyphRun.Size)); return new HeadlessGlyphRunBufferStub();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return new HeadlessHorizontalGlyphRunBufferStub();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return new HeadlessPositionedGlyphRunBufferStub();
} }
class HeadlessGeometryStub : IGeometryImpl class HeadlessGeometryStub : IGeometryImpl
@ -203,6 +213,26 @@ namespace Avalonia.Headless
public Matrix Transform { get; } public Matrix Transform { get; }
} }
class HeadlessGlyphRunBufferStub : IGlyphRunBuffer
{
public Span<ushort> GlyphIndices => Span<ushort>.Empty;
public IGlyphRunImpl Build()
{
return new HeadlessGlyphRunStub();
}
}
class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer
{
public Span<float> GlyphPositions => Span<float>.Empty;
}
class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer
{
public Span<System.Drawing.PointF> GlyphPositions => Span<System.Drawing.PointF>.Empty;
}
class HeadlessGlyphRunStub : IGlyphRunImpl class HeadlessGlyphRunStub : IGlyphRunImpl
{ {
public void Dispose() public void Dispose()

24
src/Avalonia.Headless/HeadlessPlatformStubs.cs

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

7
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -358,7 +358,6 @@
<x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double> <x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double>
<x:Double x:Key="SliderTrackThemeHeight">2</x:Double> <x:Double x:Key="SliderTrackThemeHeight">2</x:Double>
<Thickness x:Key="SliderBorderThemeThickness">0</Thickness> <Thickness x:Key="SliderBorderThemeThickness">0</Thickness>
<FontWeight x:Key="SliderHeaderThemeFontWeight">Normal</FontWeight>
<StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
@ -375,8 +374,6 @@
<StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" /> <StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" /> <StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" /> <StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SliderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" /> <StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" /> <StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" /> <StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" />
@ -424,7 +421,6 @@
<!-- Resources for DatePicker.xaml--> <!-- Resources for DatePicker.xaml-->
<StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" /> <StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" /> <StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="DatePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
@ -455,8 +451,6 @@
<!-- Resources for TimePicker.xaml --> <!-- Resources for TimePicker.xaml -->
<StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" /> <StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" /> <StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="TimePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TimePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
@ -595,7 +589,6 @@
<StaticResource x:Key="CalendarDatePickerTextForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="CalendarDatePickerTextForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="CalendarDatePickerTextForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" /> <StaticResource x:Key="CalendarDatePickerTextForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerTextForegroundSelected" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="CalendarDatePickerTextForegroundSelected" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarDatePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" /> <StaticResource x:Key="CalendarDatePickerBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" /> <StaticResource x:Key="CalendarDatePickerBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
<StaticResource x:Key="CalendarDatePickerBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" /> <StaticResource x:Key="CalendarDatePickerBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />

7
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@ -354,7 +354,6 @@
<x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double> <x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double>
<x:Double x:Key="SliderTrackThemeHeight">2</x:Double> <x:Double x:Key="SliderTrackThemeHeight">2</x:Double>
<Thickness x:Key="SliderBorderThemeThickness">0</Thickness> <Thickness x:Key="SliderBorderThemeThickness">0</Thickness>
<FontWeight x:Key="SliderHeaderThemeFontWeight">Normal</FontWeight>
<StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
@ -371,8 +370,6 @@
<StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" /> <StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" /> <StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" /> <StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SliderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" /> <StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" /> <StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" /> <StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" />
@ -420,7 +417,6 @@
<!-- Resources for DatePicker.xaml--> <!-- Resources for DatePicker.xaml-->
<StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" /> <StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" /> <StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="DatePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
@ -450,8 +446,6 @@
<!-- Resources for TimePicker.xaml --> <!-- Resources for TimePicker.xaml -->
<StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" /> <StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" /> <StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="TimePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TimePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
@ -589,7 +583,6 @@
<StaticResource x:Key="CalendarDatePickerTextForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="CalendarDatePickerTextForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="CalendarDatePickerTextForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" /> <StaticResource x:Key="CalendarDatePickerTextForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerTextForegroundSelected" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="CalendarDatePickerTextForegroundSelected" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarDatePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" /> <StaticResource x:Key="CalendarDatePickerBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" /> <StaticResource x:Key="CalendarDatePickerBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
<StaticResource x:Key="CalendarDatePickerBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" /> <StaticResource x:Key="CalendarDatePickerBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />

13
src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml

@ -28,7 +28,6 @@
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
@ -84,18 +83,8 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*"> <Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}">
<ContentPresenter Name="HeaderContentPresenter" Grid.Row="0"
Content="{TemplateBinding Header}"
Foreground="{DynamicResource DatePickerHeaderForeground}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{DynamicResource DatePickerTopHeaderMargin}"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"/>
<Button Name="PART_FlyoutButton" <Button Name="PART_FlyoutButton"
Grid.Row="1"
Theme="{StaticResource FluentDatePickerFlyoutButton}" Theme="{StaticResource FluentDatePickerFlyoutButton}"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"

30
src/Avalonia.Themes.Fluent/Controls/Slider.xaml

@ -99,18 +99,8 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid
Name="grid"
Margin="{TemplateBinding Padding}"
RowDefinitions="Auto, *">
<ContentPresenter
x:Name="HeaderContentPresenter"
Grid.Row="0"
Margin="{DynamicResource SliderTopHeaderMargin}"
TextElement.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}"
TextElement.Foreground="{DynamicResource SliderHeaderForeground}" />
<Grid x:Name="SliderContainer" <Grid x:Name="SliderContainer"
Grid.Row="1" Margin="{TemplateBinding Padding}"
Background="{DynamicResource SliderContainerBackground}"> Background="{DynamicResource SliderContainerBackground}">
<Grid.Styles> <Grid.Styles>
<Style Selector="TickBar"> <Style Selector="TickBar">
@ -191,7 +181,6 @@
</Track> </Track>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
</Border> </Border>
</DataValidationErrors> </DataValidationErrors>
</ControlTemplate> </ControlTemplate>
@ -205,19 +194,10 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid
Name="grid"
Margin="{TemplateBinding Padding}"
RowDefinitions="Auto, *">
<ContentPresenter
x:Name="HeaderContentPresenter"
Grid.Row="0"
Margin="{DynamicResource SliderTopHeaderMargin}"
TextElement.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}"
TextElement.Foreground="{DynamicResource SliderHeaderForeground}" />
<Grid x:Name="SliderContainer" <Grid x:Name="SliderContainer"
Grid.Row="1" Grid.Row="1"
Background="{DynamicResource SliderContainerBackground}"> Background="{DynamicResource SliderContainerBackground}"
Margin="{TemplateBinding Padding}">
<Grid.Styles> <Grid.Styles>
<Style Selector="TickBar"> <Style Selector="TickBar">
<Setter Property="ReservedSpace" Value="{Binding #PART_Track.Thumb.Bounds}" /> <Setter Property="ReservedSpace" Value="{Binding #PART_Track.Thumb.Bounds}" />
@ -298,7 +278,6 @@
</Track> </Track>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
</Border> </Border>
</DataValidationErrors> </DataValidationErrors>
</ControlTemplate> </ControlTemplate>
@ -326,9 +305,6 @@
<!-- Disabled State --> <!-- Disabled State -->
<Style Selector="^:disabled"> <Style Selector="^:disabled">
<Style Selector="^ /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource SliderHeaderForegroundDisabled}" />
</Style>
<Style Selector="^ /template/ RepeatButton#PART_DecreaseButton"> <Style Selector="^ /template/ RepeatButton#PART_DecreaseButton">
<Setter Property="Background" Value="{DynamicResource SliderTrackValueFillDisabled}" /> <Setter Property="Background" Value="{DynamicResource SliderTrackValueFillDisabled}" />
</Style> </Style>

20
src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml

@ -31,7 +31,6 @@
<x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double> <x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double>
<Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness> <Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness>
<Thickness x:Key="TimePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
<x:Double x:Key="TimePickerThemeMinWidth">242</x:Double> <x:Double x:Key="TimePickerThemeMinWidth">242</x:Double>
@ -83,19 +82,8 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*"> <Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}">
<ContentPresenter x:Name="HeaderContentPresenter"
Grid.Row="0"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{DynamicResource TimePickerTopHeaderMargin}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
Foreground="{DynamicResource TimePickerHeaderForeground}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top" />
<Button x:Name="PART_FlyoutButton" <Button x:Name="PART_FlyoutButton"
Grid.Row="1"
Theme="{StaticResource FluentTimePickerFlyoutButton}" Theme="{StaticResource FluentTimePickerFlyoutButton}"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
@ -172,11 +160,7 @@
</DataValidationErrors> </DataValidationErrors>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Style Selector="^:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource TimePickerHeaderForegroundDisabled}"/>
</Style>
<Style Selector="^:disabled /template/ Rectangle"> <Style Selector="^:disabled /template/ Rectangle">
<Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/> <Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/>
</Style> </Style>

13
src/Avalonia.Themes.Simple/Controls/DatePicker.xaml

@ -30,7 +30,6 @@
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
@ -91,19 +90,9 @@
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>
<Grid Name="LayoutRoot" <Grid Name="LayoutRoot"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}">
RowDefinitions="Auto,*">
<ContentPresenter Name="HeaderContentPresenter"
Grid.Row="0"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
Margin="{DynamicResource DatePickerTopHeaderMargin}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}" />
<Button Name="PART_FlyoutButton" <Button Name="PART_FlyoutButton"
Grid.Row="1"
MinWidth="{DynamicResource DatePickerThemeMinWidth}" MinWidth="{DynamicResource DatePickerThemeMinWidth}"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}" MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

19
src/Avalonia.Themes.Simple/Controls/TimePicker.xaml

@ -33,7 +33,6 @@
<x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double> <x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double>
<Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness> <Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness>
<Thickness x:Key="TimePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
<x:Double x:Key="TimePickerThemeMinWidth">242</x:Double> <x:Double x:Key="TimePickerThemeMinWidth">242</x:Double>
@ -90,20 +89,8 @@
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>
<Grid Name="LayoutRoot" <Grid Name="LayoutRoot"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}">
RowDefinitions="Auto,*">
<ContentPresenter x:Name="HeaderContentPresenter"
Grid.Row="0"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
Margin="{DynamicResource TimePickerTopHeaderMargin}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Foreground="{DynamicResource ThemeForegroundColor}" />
<Button x:Name="PART_FlyoutButton" <Button x:Name="PART_FlyoutButton"
Grid.Row="1"
MinWidth="{DynamicResource TimePickerThemeMinWidth}" MinWidth="{DynamicResource TimePickerThemeMinWidth}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}" MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@ -181,10 +168,6 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Style Selector="^:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
</Style>
<Style Selector="^:disabled /template/ Rectangle"> <Style Selector="^:disabled /template/ Rectangle">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" /> <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style> </Style>

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

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

90
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

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

184
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -1,7 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -12,6 +12,8 @@ using Avalonia.OpenGL.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using SkiaSharp; using SkiaSharp;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
@ -33,13 +35,17 @@ namespace Avalonia.Skia
} }
var gl = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>(); var gl = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
if (gl != null) if (gl != null)
_skiaGpu = new GlSkiaGpu(gl, maxResourceBytes); _skiaGpu = new GlSkiaGpu(gl, maxResourceBytes);
//TODO: SKFont crashes when disposed in finalizer so we keep it alive
GC.SuppressFinalize(s_font);
} }
public bool SupportsIndividualRoundRects => true;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat { get; }
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect); public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2); public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
@ -64,7 +70,7 @@ namespace Avalonia.Skia
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) 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."); throw new InvalidOperationException("PlatformImpl can't be null.");
} }
@ -228,133 +234,95 @@ namespace Avalonia.Skia
return new WriteableBitmapImpl(size, dpi, format, alphaFormat); return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
} }
private static readonly SKFont s_font = new SKFont public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi)
{
Subpixel = true,
Edging = SKFontEdging.SubpixelAntialias,
Hinting = SKFontHinting.Full,
LinearMetrics = true
};
private static readonly ThreadLocal<SKTextBlobBuilder> s_textBlobBuilderThreadLocal = new ThreadLocal<SKTextBlobBuilder>(() => new SKTextBlobBuilder());
/// <inheritdoc />
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
{ {
var count = glyphRun.GlyphIndices.Count; if (_skiaGpu is IOpenGlAwareSkiaGpu glAware)
var textBlobBuilder = s_textBlobBuilderThreadLocal.Value; return glAware.CreateOpenGlBitmap(size, dpi);
if (_skiaGpu == null)
var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; throw new PlatformNotSupportedException("GPU acceleration is not available");
throw new PlatformNotSupportedException(
"Current GPU acceleration backend does not support OpenGL integration");
}
var typeface = glyphTypeface.Typeface; public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
=> new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
s_font.Size = (float)glyphRun.FontRenderingEmSize; public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
s_font.Typeface = typeface; => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
s_font.Embolden = glyphTypeface.IsFakeBold;
s_font.SkewX = glyphTypeface.IsFakeItalic ? -0.2f : 0;
SKTextBlob textBlob; public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
=> new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight); private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer
{
protected readonly SKTextBlobBuilder _builder;
protected readonly SKFont _font;
if (glyphRun.GlyphOffsets == null) public SKGlyphRunBufferBase(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
if (glyphTypeface.IsFixedPitch) _builder = new SKTextBlobBuilder();
{
var buffer = textBlobBuilder.AllocateRun(s_font, glyphRun.GlyphIndices.Count, 0, 0);
var glyphs = buffer.GetGlyphSpan();
for (int i = 0; i < glyphs.Length; i++) var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
{
glyphs[i] = glyphRun.GlyphIndices[i];
}
textBlob = textBlobBuilder.Build(); _font = new SKFont
}
else
{ {
var buffer = textBlobBuilder.AllocateHorizontalRun(s_font, count, 0); Subpixel = true,
Edging = SKFontEdging.SubpixelAntialias,
var positions = buffer.GetPositionSpan(); Hinting = SKFontHinting.Full,
LinearMetrics = true,
var width = 0d; Size = fontRenderingEmSize,
Typeface = glyphTypefaceImpl.Typeface,
for (var i = 0; i < count; i++) Embolden = glyphTypefaceImpl.IsFakeBold,
{ SkewX = glyphTypefaceImpl.IsFakeItalic ? -0.2f : 0
positions[i] = (float)width; };
}
if (glyphRun.GlyphAdvances == null)
{
width += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
}
else
{
width += glyphRun.GlyphAdvances[i];
}
}
var glyphs = buffer.GetGlyphSpan();
for (int i = 0; i < glyphs.Length; i++) public abstract Span<ushort> GlyphIndices { get; }
{
glyphs[i] = glyphRun.GlyphIndices[i];
}
textBlob = textBlobBuilder.Build(); public IGlyphRunImpl Build()
}
}
else
{ {
var buffer = textBlobBuilder.AllocatePositionedRun(s_font, count); return new GlyphRunImpl(_builder.Build());
}
var glyphPositions = buffer.GetPositionSpan(); }
var currentX = 0.0; private sealed class SKGlyphRunBuffer : SKGlyphRunBufferBase
{
private readonly SKRunBuffer _buffer;
for (var i = 0; i < count; i++) public SKGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
{ {
var glyphOffset = glyphRun.GlyphOffsets[i]; _buffer = _builder.AllocateRun(_font, length, 0, 0);
}
glyphPositions[i] = new SKPoint((float)(currentX + glyphOffset.X), (float)glyphOffset.Y);
if (glyphRun.GlyphAdvances == null)
{
currentX += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
}
else
{
currentX += glyphRun.GlyphAdvances[i];
}
}
var glyphs = buffer.GetGlyphSpan(); public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan();
}
for (int i = 0; i < glyphs.Length; i++) private sealed class SKHorizontalGlyphRunBuffer : SKGlyphRunBufferBase, IHorizontalGlyphRunBuffer
{ {
glyphs[i] = glyphRun.GlyphIndices[i]; private readonly SKHorizontalRunBuffer _buffer;
}
textBlob = textBlobBuilder.Build(); public SKHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
{
_buffer = _builder.AllocateHorizontalRun(_font, length, 0);
} }
return new GlyphRunImpl(textBlob); public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan();
public Span<float> GlyphPositions => _buffer.GetPositionSpan();
} }
public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) private sealed class SKPositionedGlyphRunBuffer : SKGlyphRunBufferBase, IPositionedGlyphRunBuffer
{ {
if (_skiaGpu is IOpenGlAwareSkiaGpu glAware) private readonly SKPositionedRunBuffer _buffer;
return glAware.CreateOpenGlBitmap(size, dpi);
if (_skiaGpu == null)
throw new PlatformNotSupportedException("GPU acceleration is not available");
throw new PlatformNotSupportedException(
"Current GPU acceleration backend does not support OpenGL integration");
}
public bool SupportsIndividualRoundRects => true; public SKPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
{
_buffer = _builder.AllocatePositionedRun(_font, length);
}
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan();
public PixelFormat DefaultPixelFormat { get; } public Span<PointF> GlyphPositions => MemoryMarshal.Cast<SKPoint, PointF>(_buffer.GetPositionSpan());
}
} }
} }

2
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -31,7 +31,7 @@ namespace Avalonia.Skia
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; var font = ((GlyphTypefaceImpl)typeface).Font;
font.Shape(buffer); font.Shape(buffer);

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

@ -12,6 +12,8 @@ using SharpDX.DirectWrite;
using GlyphRun = Avalonia.Media.GlyphRun; using GlyphRun = Avalonia.Media.GlyphRun;
using TextAlignment = Avalonia.Media.TextAlignment; using TextAlignment = Avalonia.Media.TextAlignment;
using SharpDX.Mathematics.Interop; using SharpDX.Mathematics.Interop;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Avalonia namespace Avalonia
{ {
@ -160,7 +162,7 @@ namespace Avalonia.Direct2D1
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) 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."); throw new InvalidOperationException("PlatformImpl can't be null.");
} }
@ -258,69 +260,66 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride); return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride);
} }
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) private class DWGlyphRunBuffer : IGlyphRunBuffer
{ {
var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; protected readonly SharpDX.DirectWrite.GlyphRun _dwRun;
var glyphCount = glyphRun.GlyphIndices.Count; public DWGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
var run = new SharpDX.DirectWrite.GlyphRun
{ {
FontFace = glyphTypeface.FontFace, var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
FontSize = (float)glyphRun.FontRenderingEmSize
};
var indices = new short[glyphCount];
for (var i = 0; i < glyphCount; i++) _dwRun = new SharpDX.DirectWrite.GlyphRun
{ {
indices[i] = (short)glyphRun.GlyphIndices[i]; FontFace = glyphTypefaceImpl.FontFace,
FontSize = fontRenderingEmSize,
Indices = new short[length]
};
} }
run.Indices = indices; public Span<ushort> GlyphIndices => MemoryMarshal.Cast<short, ushort>(_dwRun.Indices.AsSpan());
run.Advances = new float[glyphCount];
var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight); public IGlyphRunImpl Build()
if (glyphRun.GlyphAdvances == null)
{ {
for (var i = 0; i < glyphCount; i++) return new GlyphRunImpl(_dwRun);
{
var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
run.Advances[i] = advance;
}
} }
else }
{
for (var i = 0; i < glyphCount; i++)
{
var advance = (float)glyphRun.GlyphAdvances[i];
run.Advances[i] = advance; private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer
} {
public DWHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
: base(glyphTypeface, fontRenderingEmSize, length)
{
_dwRun.Advances = new float[length];
} }
if (glyphRun.GlyphOffsets == null) public Span<float> GlyphPositions => _dwRun.Advances.AsSpan();
}
private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer
{
public DWPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
: base(glyphTypeface, fontRenderingEmSize, length)
{ {
return new GlyphRunImpl(run); _dwRun.Advances = new float[length];
_dwRun.Offsets = new GlyphOffset[length];
} }
run.Offsets = new GlyphOffset[glyphCount]; public Span<PointF> GlyphPositions => MemoryMarshal.Cast<GlyphOffset, PointF>(_dwRun.Offsets.AsSpan());
}
for (var i = 0; i < glyphCount; i++) public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
var (x, y) = glyphRun.GlyphOffsets[i]; return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
}
run.Offsets[i] = new GlyphOffset public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
AdvanceOffset = (float)x, return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
AscenderOffset = (float)y }
};
}
return new GlyphRunImpl(run); public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
} }
public bool SupportsIndividualRoundRects => false; public bool SupportsIndividualRoundRects => false;

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

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

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

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

@ -31,7 +31,7 @@ namespace Avalonia.Direct2D1.Media
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; var font = ((GlyphTypefaceImpl)typeface).Font;
font.Shape(buffer); font.Shape(buffer);

2
tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs

@ -185,7 +185,7 @@ namespace Avalonia.Base.UnitTests.Media
var characters = new ReadOnlySlice<char>(Enumerable.Repeat('a', count).ToArray(), start, count); var characters = new ReadOnlySlice<char>(Enumerable.Repeat('a', count).ToArray(), start, count);
return new GlyphRun(new GlyphTypeface(new MockGlyphTypeface()), 10, characters, glyphIndices, glyphAdvances, return new GlyphRun(new MockGlyphTypeface(), 10, characters, glyphIndices, glyphAdvances,
glyphClusters: glyphClusters, biDiLevel: bidiLevel); glyphClusters: glyphClusters, biDiLevel: bidiLevel);
} }
} }

15
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@ -126,6 +126,21 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
class MockStreamGeometry : IStreamGeometryImpl class MockStreamGeometry : IStreamGeometryImpl
{ {
private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); private MockStreamGeometryContext _impl = new MockStreamGeometryContext();

16
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -112,12 +112,22 @@ namespace Avalonia.Benchmarks
return new MockFontManagerImpl(); return new MockFontManagerImpl();
} }
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{ {
return new NullGlyphRun(); return new MockStreamGeometryImpl();
} }
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

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

@ -64,7 +64,7 @@ namespace Avalonia.Skia.UnitTests.Media
return true; return true;
} }
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{ {
SKTypeface skTypeface; 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 glyph = typeface.GlyphTypeface.GetGlyph('a');
var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) * var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) *
(12.0 / typeface.GlyphTypeface.DesignEmHeight); (12.0 / typeface.GlyphTypeface.Metrics.DesignEmHeight);
var paragraphWidth = advance * numberOfCharactersPerLine; 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 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( var layout = new TextLayout(
text, text,

2
tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs

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

96
tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs

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

2
tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs

@ -30,7 +30,7 @@ namespace Avalonia.UnitTests
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var font = ((HarfBuzzGlyphTypefaceImpl)typeface.PlatformImpl).Font; var font = ((HarfBuzzGlyphTypefaceImpl)typeface).Font;
font.Shape(buffer); font.Shape(buffer);

2
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

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

35
tests/Avalonia.UnitTests/MockGlyphTypeface.cs

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

15
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -152,6 +152,21 @@ namespace Avalonia.UnitTests
return Mock.Of<IGeometryImpl>(); return Mock.Of<IGeometryImpl>();
} }
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return Mock.Of<IGlyphRunBuffer>();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return Mock.Of<IHorizontalGlyphRunBuffer>();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return Mock.Of<IPositionedGlyphRunBuffer>();
}
public bool SupportsIndividualRoundRects { get; set; } public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;

Loading…
Cancel
Save