Browse Source

Merge branch 'master' into feature/toggleswitch-dragging

pull/4241/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
1da6fb4aa3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      readme.md
  2. 1
      samples/ControlCatalog/MainView.xaml
  3. 60
      samples/ControlCatalog/Pages/RelativePanelPage.axaml
  4. 19
      samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs
  5. 4
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  6. 6
      src/Avalonia.Controls/Primitives/AccessText.cs
  7. 546
      src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
  8. 353
      src/Avalonia.Controls/RelativePanel.cs
  9. 9
      src/Avalonia.Controls/ResolveByNameAttribute.cs
  10. 31
      src/Avalonia.Controls/TextBlock.cs
  11. 10
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  12. 14
      src/Avalonia.Visuals/Media/FontManager.cs
  13. 2
      src/Avalonia.Visuals/Media/Fonts/FontKey.cs
  14. 11
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  15. 2
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  16. 69
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  17. 37
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  18. 3
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  19. 17
      src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
  20. 5
      src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
  21. 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  22. 36
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs
  23. 11
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  24. 49
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs
  25. 3
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
  26. 4
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  27. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  28. 7
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  29. 5
      src/Skia/Avalonia.Skia/GlyphRunImpl.cs
  30. 49
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  31. 8
      src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
  32. 93
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  33. 5
      src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
  34. 59
      tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs
  35. 2
      tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
  36. 6
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  37. 33
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  38. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  39. 2
      tests/Avalonia.UnitTests/MockFontManagerImpl.cs
  40. 1
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  41. 37
      tests/Avalonia.Visuals.UnitTests/Utilities/ReadOnlySpanTests.cs

2
readme.md

@ -16,7 +16,7 @@ To see the status of some of our features, please see our [Roadmap](https://gith
## 🚀 Getting Started
The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starer guide see our [documentation](http://avaloniaui.net/docs/quickstart/create-new-project).
The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](http://avaloniaui.net/docs/quickstart/create-new-project).
Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/Avalonia/

1
samples/ControlCatalog/MainView.xaml

@ -56,6 +56,7 @@
<TabItem Header="Pointers (Touch)"><pages:PointersPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="RelativePanel"><pages:RelativePanelPage/></TabItem>
<TabItem Header="ScrollViewer"><pages:ScrollViewerPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="SplitView"><pages:SplitViewPage/></TabItem>

60
samples/ControlCatalog/Pages/RelativePanelPage.axaml

@ -0,0 +1,60 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.RelativePanelPage">
<RelativePanel Width="620" Height="700" Margin="32">
<Border Name="Rect1" Background="Red" Height="50" Width="50">
<TextBlock Text="Rect1" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect2" Background="Blue" Height="50" Width="50" RelativePanel.AlignHorizontalCenterWithPanel="True">
<TextBlock Text="Rect2" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect3" Background="Green" Height="50" Width="50" RelativePanel.AlignRightWithPanel="True">
<TextBlock Text="Rect3" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect4" Background="Red" Height="50" Width="50" RelativePanel.AlignBottomWithPanel="True">
<TextBlock Text="Rect4" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect5" Background="Blue" Height="50" Width="50" RelativePanel.AlignBottomWithPanel="True" RelativePanel.AlignHorizontalCenterWithPanel="True">
<TextBlock Text="Rect5" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect6" Background="Green" Height="50" Width="50" RelativePanel.AlignBottomWithPanel="True" RelativePanel.AlignRightWithPanel="True">
<TextBlock Text="Rect6" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect7" Background="Blue" Height="50" RelativePanel.RightOf="{Binding ElementName=Rect1}">
<TextBlock Text="Rect7 (RightOf Rect1)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect8" Background="Green" Height="50" RelativePanel.Below="{Binding ElementName=Rect7}">
<TextBlock Text="Rect8 (Below Rect7)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect9" Background="Blue" Height="140" Width="460" RelativePanel.AlignHorizontalCenterWithPanel="True" RelativePanel.AlignVerticalCenterWithPanel="True">
<TextBlock Text="Rect9" Padding="10" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Top"/>
</Border>
<Border Name="Rect10" Background="Red" Width="50" RelativePanel.RightOf="{Binding ElementName=Rect9}" RelativePanel.AlignVerticalCenterWith="{Binding ElementName=Rect9}">
<TextBlock Text="Rect14 (RightOf Rect9, AlignVerticalCenterWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.RenderTransform>
<TransformGroup>
<RotateTransform Angle="-90"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Border>
<Border Name="Rect11" Background="Red" Height="50" RelativePanel.AlignBottomWith="{Binding ElementName=Rect9}" RelativePanel.AlignHorizontalCenterWith="{Binding ElementName=Rect9}">
<TextBlock Text="Rect11 (AlignBottomWith Rect9, AlignHorizontalCenterWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect12" Background="Red" Height="50" RelativePanel.Below="{Binding ElementName=Rect8}" RelativePanel.AlignLeftWith="{Binding ElementName=Rect7}">
<TextBlock Text="Rect12 (Below Rect8, AlignLeftWith Rect7)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect13" Background="Blue" Height="50" RelativePanel.Below="{Binding ElementName=Rect12}" RelativePanel.AlignRightWith="{Binding ElementName=Rect12}">
<TextBlock Text="Rect13 (Below Rect12, AlignRightWith Rect12)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect14" Background="Green" Height="50" RelativePanel.Above="{Binding ElementName=Rect9}" RelativePanel.AlignRightWith="{Binding ElementName=Rect9}">
<TextBlock Text="Rect14 (Above Rect9, AlignRightWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect15" Background="Red" Height="50" RelativePanel.LeftOf="{Binding ElementName=Rect2}" RelativePanel.AlignTopWith="{Binding ElementName=Rect9}">
<TextBlock Text="Rect15 (LeftOf Rect2, AlignTopWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</RelativePanel>
</UserControl>

19
samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class RelativePanelPage : UserControl
{
public RelativePanelPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

4
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -273,7 +273,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Constraint = constraint,
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize,
Text = text ?? string.Empty,
TextAlignment = TextAlignment,
@ -490,7 +490,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Text = "X",
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize,
TextAlignment = TextAlignment,
Constraint = availableSize,

6
src/Avalonia.Controls/Primitives/AccessText.cs

@ -97,9 +97,7 @@ namespace Avalonia.Controls.Primitives
{
var lastLine = TextLayout.TextLines[TextLayout.TextLines.Count - 1];
var offsetX = lastLine.LineMetrics.BaselineOrigin.X;
var lineX = offsetX + lastLine.LineMetrics.Size.Width;
var lineX = lastLine.LineMetrics.Size.Width;
var lineY = Bounds.Height - lastLine.LineMetrics.Size.Height;
@ -117,7 +115,7 @@ namespace Avalonia.Controls.Primitives
continue;
}
var currentX = textLine.LineMetrics.BaselineOrigin.X;
var currentX = 0.0;
foreach (var textRun in textLine.TextRuns)
{

546
src/Avalonia.Controls/RelativePanel.AttachedProperties.cs

@ -0,0 +1,546 @@
using Avalonia.Layout;
#nullable enable
namespace Avalonia.Controls
{
public partial class RelativePanel
{
private static void OnAlignPropertiesChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
if (d is Layoutable layoutable && layoutable.Parent is Layoutable layoutableParent)
{
layoutableParent.InvalidateArrange();
}
}
static RelativePanel()
{
ClipToBoundsProperty.OverrideDefaultValue<RelativePanel>(true);
AboveProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignBottomWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignBottomWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignHorizontalCenterWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignHorizontalCenterWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignLeftWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignLeftWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignRightWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignRightWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignTopWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignTopWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignVerticalCenterWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignVerticalCenterWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
BelowProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
LeftOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
LeftOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
}
/// <summary>
/// Gets the value of the RelativePanel.Above XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.Above XAML attached property value of the specified object.
/// (The element to position this element above.)
/// </returns>
[ResolveByName]
public static object GetAbove(AvaloniaObject obj)
{
return (object)obj.GetValue(AboveProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.Above XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to position this element above.)</param>
public static void SetAbove(AvaloniaObject obj, object value)
{
obj.SetValue(AboveProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AboveProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AboveProperty =
AvaloniaProperty.RegisterAttached<Layoutable, object>("Above", typeof(RelativePanel));
/// <summary>
/// Gets the value of the RelativePanel.AlignBottomWithPanel XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignBottomWithPanel XAML attached property value of the specified
/// object. (true to align this element's bottom edge with the panel's bottom edge;
/// otherwise, false.)
/// </returns>
public static bool GetAlignBottomWithPanel(AvaloniaObject obj)
{
return (bool)obj.GetValue(AlignBottomWithPanelProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.Above XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">
/// The value to set. (true to align this element's bottom edge with the panel's
/// bottom edge; otherwise, false.)
/// </param>
public static void SetAlignBottomWithPanel(AvaloniaObject obj, bool value)
{
obj.SetValue(AlignBottomWithPanelProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignBottomWithPanelProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<bool> AlignBottomWithPanelProperty =
AvaloniaProperty.RegisterAttached<Layoutable, bool>("AlignBottomWithPanel", typeof(RelativePanel));
/// <summary>
/// Gets the value of the RelativePanel.AlignBottomWith XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignBottomWith XAML attached property value of the specified object.
/// (The element to align this element's bottom edge with.)
/// </returns>
[ResolveByName]
public static object GetAlignBottomWith(AvaloniaObject obj)
{
return (object)obj.GetValue(AlignBottomWithProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.Above XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's bottom edge with.)</param>
public static void SetAlignBottomWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignBottomWithProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignBottomWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignBottomWithProperty =
AvaloniaProperty.RegisterAttached<Layoutable, object>("AlignBottomWith", typeof(RelativePanel));
/// <summary>
/// Gets the value of the RelativePanel.AlignHorizontalCenterWithPanel XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignHorizontalCenterWithPanel XAML attached property value
/// of the specified object. (true to horizontally center this element in the panel;
/// otherwise, false.)
/// </returns>
public static bool GetAlignHorizontalCenterWithPanel(AvaloniaObject obj)
{
return (bool)obj.GetValue(AlignHorizontalCenterWithPanelProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.Above XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">
/// The value to set. (true to horizontally center this element in the panel; otherwise,
/// false.)
/// </param>
public static void SetAlignHorizontalCenterWithPanel(AvaloniaObject obj, bool value)
{
obj.SetValue(AlignHorizontalCenterWithPanelProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignHorizontalCenterWithPanelProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<bool> AlignHorizontalCenterWithPanelProperty =
AvaloniaProperty.RegisterAttached<Layoutable, bool>("AlignHorizontalCenterWithPanel", typeof(RelativePanel), false);
/// <summary>
/// Gets the value of the RelativePanel.AlignHorizontalCenterWith XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignHorizontalCenterWith XAML attached property value of the
/// specified object. (The element to align this element's horizontal center with.)
/// </returns>
[ResolveByName]
public static object GetAlignHorizontalCenterWith(AvaloniaObject obj)
{
return (object)obj.GetValue(AlignHorizontalCenterWithProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.Above XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's horizontal center with.)</param>
public static void SetAlignHorizontalCenterWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignHorizontalCenterWithProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignHorizontalCenterWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignHorizontalCenterWithProperty =
AvaloniaProperty.RegisterAttached<Layoutable, object>("AlignHorizontalCenterWith", typeof(object), typeof(RelativePanel));
/// <summary>
/// Gets the value of the RelativePanel.AlignLeftWithPanel XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignLeftWithPanel XAML attached property value of the specified
/// object. (true to align this element's left edge with the panel's left edge; otherwise,
/// false.)
/// </returns>
public static bool GetAlignLeftWithPanel(AvaloniaObject obj)
{
return (bool)obj.GetValue(AlignLeftWithPanelProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.Above XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">
/// The value to set. (true to align this element's left edge with the panel's left
/// edge; otherwise, false.)
/// </param>
public static void SetAlignLeftWithPanel(AvaloniaObject obj, bool value)
{
obj.SetValue(AlignLeftWithPanelProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignLeftWithPanelProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<bool> AlignLeftWithPanelProperty =
AvaloniaProperty.RegisterAttached<Layoutable, bool>("AlignLeftWithPanel", typeof(RelativePanel), false);
/// <summary>
/// Gets the value of the RelativePanel.AlignLeftWith XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignLeftWith XAML attached property value of the specified
/// object. (The element to align this element's left edge with.)
/// </returns>
[ResolveByName]
public static object GetAlignLeftWith(AvaloniaObject obj)
{
return (object)obj.GetValue(AlignLeftWithProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.Above XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's left edge with.)</param>
public static void SetAlignLeftWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignLeftWithProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignLeftWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignLeftWithProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignLeftWith");
/// <summary>
/// Gets the value of the RelativePanel.AlignRightWithPanel XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignRightWithPanel XAML attached property value of the specified
/// object. (true to align this element's right edge with the panel's right edge;
/// otherwise, false.)
/// </returns>
public static bool GetAlignRightWithPanel(AvaloniaObject obj)
{
return (bool)obj.GetValue(AlignRightWithPanelProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.Above XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">
/// The value to set. (true to align this element's right edge with the panel's right
/// edge; otherwise, false.)
/// </param>
public static void SetAlignRightWithPanel(AvaloniaObject obj, bool value)
{
obj.SetValue(AlignRightWithPanelProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignRightWithPanelProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<bool> AlignRightWithPanelProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, bool>("AlignRightWithPanel", false);
/// <summary>
/// Gets the value of the RelativePanel.AlignRightWith XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignRightWith XAML attached property value of the specified
/// object. (The element to align this element's right edge with.)
/// </returns>
[ResolveByName]
public static object GetAlignRightWith(AvaloniaObject obj)
{
return (object)obj.GetValue(AlignRightWithProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.AlignRightWith XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's right edge with.)</param>
public static void SetAlignRightWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignRightWithProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignRightWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignRightWithProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignRightWith");
/// <summary>
/// Gets the value of the RelativePanel.AlignTopWithPanel XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignTopWithPanel XAML attached property value of the specified
/// object. (true to align this element's top edge with the panel's top edge; otherwise,
/// false.)
/// </returns>
public static bool GetAlignTopWithPanel(AvaloniaObject obj)
{
return (bool)obj.GetValue(AlignTopWithPanelProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.AlignTopWithPanel XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">
/// The value to set. (true to align this element's top edge with the panel's top
/// edge; otherwise, false.)
/// </param>
public static void SetAlignTopWithPanel(AvaloniaObject obj, bool value)
{
obj.SetValue(AlignTopWithPanelProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignTopWithPanelProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<bool> AlignTopWithPanelProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, bool>("AlignTopWithPanel", false);
/// <summary>
/// Gets the value of the RelativePanel.AlignTopWith XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>The value to set. (The element to align this element's top edge with.)</returns>
[ResolveByName]
public static object GetAlignTopWith(AvaloniaObject obj)
{
return (object)obj.GetValue(AlignTopWithProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.AlignTopWith XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's top edge with.)</param>
public static void SetAlignTopWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignTopWithProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignTopWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignTopWithProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignTopWith");
/// <summary>
/// Gets the value of the RelativePanel.AlignVerticalCenterWithPanel XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.AlignVerticalCenterWithPanel XAML attached property value of
/// the specified object. (true to vertically center this element in the panel; otherwise,
/// false.)
/// </returns>
public static bool GetAlignVerticalCenterWithPanel(AvaloniaObject obj)
{
return (bool)obj.GetValue(AlignVerticalCenterWithPanelProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.AlignVerticalCenterWithPanel XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">
/// The value to set. (true to vertically center this element in the panel; otherwise,
/// false.)
/// </param>
public static void SetAlignVerticalCenterWithPanel(AvaloniaObject obj, bool value)
{
obj.SetValue(AlignVerticalCenterWithPanelProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignVerticalCenterWithPanelProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<bool> AlignVerticalCenterWithPanelProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, bool>("AlignVerticalCenterWithPanel", false);
/// <summary>
/// Gets the value of the RelativePanel.AlignVerticalCenterWith XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>The value to set. (The element to align this element's vertical center with.)</returns>
[ResolveByName]
public static object GetAlignVerticalCenterWith(AvaloniaObject obj)
{
return (object)obj.GetValue(AlignVerticalCenterWithProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.AlignVerticalCenterWith XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's horizontal center with.)</param>
public static void SetAlignVerticalCenterWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignVerticalCenterWithProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignVerticalCenterWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignVerticalCenterWithProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignVerticalCenterWith");
/// <summary>
/// Gets the value of the RelativePanel.Below XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.Below XAML attached property value of the specified object.
/// (The element to position this element below.)
/// </returns>
[ResolveByName]
public static object GetBelow(AvaloniaObject obj)
{
return (object)obj.GetValue(BelowProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.Above XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to position this element below.)</param>
public static void SetBelow(AvaloniaObject obj, object value)
{
obj.SetValue(BelowProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.BelowProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> BelowProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("Below");
/// <summary>
/// Gets the value of the RelativePanel.LeftOf XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.LeftOf XAML attached property value of the specified object.
/// (The element to position this element to the left of.)
/// </returns>
[ResolveByName]
public static object GetLeftOf(AvaloniaObject obj)
{
return (object)obj.GetValue(LeftOfProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.LeftOf XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to position this element to the left of.)</param>
public static void SetLeftOf(AvaloniaObject obj, object value)
{
obj.SetValue(LeftOfProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.LeftOfProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> LeftOfProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("LeftOf");
/// <summary>
/// Gets the value of the RelativePanel.RightOf XAML attached property for the target element.
/// </summary>
/// <param name="obj">The object from which the property value is read.</param>
/// <returns>
/// The RelativePanel.RightOf XAML attached property value of the specified object.
/// (The element to position this element to the right of.)
/// </returns>
[ResolveByName]
public static object GetRightOf(AvaloniaObject obj)
{
return (object)obj.GetValue(RightOfProperty);
}
/// <summary>
/// Sets the value of the RelativePanel.RightOf XAML attached property for a target element.
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to position this element to the right of.)</param>
public static void SetRightOf(AvaloniaObject obj, object value)
{
obj.SetValue(RightOfProperty, value);
}
/// <summary>
/// Identifies the <see cref="RelativePanel.RightOfProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> RightOfProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("RightOf");
}
}

353
src/Avalonia.Controls/RelativePanel.cs

@ -0,0 +1,353 @@
/// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Layout;
#nullable enable
namespace Avalonia.Controls
{
public partial class RelativePanel : Panel
{
private readonly Graph _childGraph;
public RelativePanel() => _childGraph = new Graph();
protected override Size MeasureOverride(Size availableSize)
{
foreach (var child in Children)
{
child?.Measure(availableSize);
}
return availableSize;
}
protected override Size ArrangeOverride(Size arrangeSize)
{
_childGraph.Reset(arrangeSize);
foreach (var child in Children.OfType<Layoutable>())
{
if (child == null)
continue;
var node = _childGraph.AddNode(child);
node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child));
node.AlignTopWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignTopWithProperty, child));
node.AlignRightWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignRightWithProperty, child));
node.AlignBottomWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignBottomWithProperty, child));
node.LeftOfNode = _childGraph.AddLink(node, GetDependencyElement(LeftOfProperty, child));
node.AboveNode = _childGraph.AddLink(node, GetDependencyElement(AboveProperty, child));
node.RightOfNode = _childGraph.AddLink(node, GetDependencyElement(RightOfProperty, child));
node.BelowNode = _childGraph.AddLink(node, GetDependencyElement(BelowProperty, child));
node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child));
node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child));
}
if (_childGraph.CheckCyclic())
{
throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete.");
}
var size = new Size();
foreach (var child in Children)
{
if (child.Bounds.Bottom > size.Height)
{
size = size.WithHeight(child.Bounds.Bottom);
}
if (child.Bounds.Right > size.Width)
{
size = size.WithWidth(child.Bounds.Right);
}
}
if (VerticalAlignment == VerticalAlignment.Stretch)
{
size = size.WithHeight(arrangeSize.Height);
}
if (HorizontalAlignment == HorizontalAlignment.Stretch)
{
size = size.WithWidth(arrangeSize.Width);
}
return size;
}
private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child)
{
var dependency = child.GetValue(property);
if (dependency is Layoutable layoutable)
{
if (Children.Contains((ILayoutable)layoutable))
return layoutable;
throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}");
}
return null;
}
private class GraphNode
{
public Point Position { get; set; }
public bool Arranged { get; set; }
public Layoutable Element { get; }
public HashSet<GraphNode> OutgoingNodes { get; }
public GraphNode? AlignLeftWithNode { get; set; }
public GraphNode? AlignTopWithNode { get; set; }
public GraphNode? AlignRightWithNode { get; set; }
public GraphNode? AlignBottomWithNode { get; set; }
public GraphNode? LeftOfNode { get; set; }
public GraphNode? AboveNode { get; set; }
public GraphNode? RightOfNode { get; set; }
public GraphNode? BelowNode { get; set; }
public GraphNode? AlignHorizontalCenterWith { get; set; }
public GraphNode? AlignVerticalCenterWith { get; set; }
public GraphNode(Layoutable element)
{
OutgoingNodes = new HashSet<GraphNode>();
Element = element;
}
}
private class Graph
{
private readonly Dictionary<AvaloniaObject, GraphNode> _nodeDic;
private Size _arrangeSize;
public Graph()
{
_nodeDic = new Dictionary<AvaloniaObject, GraphNode>();
}
public GraphNode? AddLink(GraphNode from, Layoutable? to)
{
if (to == null)
return null;
GraphNode nodeTo;
if (_nodeDic.ContainsKey(to))
{
nodeTo = _nodeDic[to];
}
else
{
nodeTo = new GraphNode(to);
_nodeDic[to] = nodeTo;
}
from.OutgoingNodes.Add(nodeTo);
return nodeTo;
}
public GraphNode AddNode(Layoutable value)
{
if (!_nodeDic.ContainsKey(value))
{
var node = new GraphNode(value);
_nodeDic.Add(value, node);
return node;
}
return _nodeDic[value];
}
public void Reset(Size arrangeSize)
{
_arrangeSize = arrangeSize;
_nodeDic.Clear();
}
public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null);
private bool CheckCyclic(IEnumerable<GraphNode> nodes, HashSet<Layoutable>? set)
{
set ??= new HashSet<Layoutable>();
foreach (var node in nodes)
{
if (!node.Arranged && node.OutgoingNodes.Count == 0)
{
ArrangeChild(node, true);
continue;
}
if (node.OutgoingNodes.All(item => item.Arranged))
{
ArrangeChild(node);
continue;
}
if (!set.Add(node.Element))
return true;
return CheckCyclic(node.OutgoingNodes, set);
}
return false;
}
private void ArrangeChild(GraphNode node, bool ignoneSibling = false)
{
var child = node.Element;
var childSize = child.DesiredSize;
var childPos = new Point();
if (GetAlignHorizontalCenterWithPanel(child))
{
childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2);
}
if (GetAlignVerticalCenterWithPanel(child))
{
childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2);
}
var alignLeftWithPanel = GetAlignLeftWithPanel(child);
var alignTopWithPanel = GetAlignTopWithPanel(child);
var alignRightWithPanel = GetAlignRightWithPanel(child);
var alignBottomWithPanel = GetAlignBottomWithPanel(child);
if (!ignoneSibling)
{
if (node.LeftOfNode != null)
{
childPos = childPos.WithX(node.LeftOfNode.Position.X - childSize.Width);
}
if (node.AboveNode != null)
{
childPos = childPos.WithY(node.AboveNode.Position.Y - childSize.Height);
}
if (node.RightOfNode != null)
{
childPos = childPos.WithX(node.RightOfNode.Position.X + node.RightOfNode.Element.DesiredSize.Width);
}
if (node.BelowNode != null)
{
childPos = childPos.WithY(node.BelowNode.Position.Y + node.BelowNode.Element.DesiredSize.Height);
}
if (node.AlignHorizontalCenterWith != null)
{
childPos = childPos.WithX(node.AlignHorizontalCenterWith.Position.X +
(node.AlignHorizontalCenterWith.Element.DesiredSize.Width - childSize.Width) / 2);
}
if (node.AlignVerticalCenterWith != null)
{
childPos = childPos.WithY(node.AlignVerticalCenterWith.Position.Y +
(node.AlignVerticalCenterWith.Element.DesiredSize.Height - childSize.Height) / 2);
}
if (node.AlignLeftWithNode != null)
{
childPos = childPos.WithX(node.AlignLeftWithNode.Position.X);
}
if (node.AlignTopWithNode != null)
{
childPos = childPos.WithY(node.AlignTopWithNode.Position.Y);
}
if (node.AlignRightWithNode != null)
{
childPos = childPos.WithX(node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width);
}
if (node.AlignBottomWithNode != null)
{
childPos = childPos.WithY(node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height);
}
}
if (alignLeftWithPanel)
{
if (node.AlignRightWithNode != null)
{
childPos = childPos.WithX((node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width) / 2);
}
else
{
childPos = childPos.WithX(0);
}
}
if (alignTopWithPanel)
{
if (node.AlignBottomWithNode != null)
{
childPos = childPos.WithY((node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height) / 2);
}
else
{
childPos = childPos.WithY(0);
}
}
if (alignRightWithPanel)
{
if (alignLeftWithPanel)
{
childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2);
}
else if (node.AlignLeftWithNode == null)
{
childPos = childPos.WithX(_arrangeSize.Width - childSize.Width);
}
else
{
childPos = childPos.WithX((_arrangeSize.Width + node.AlignLeftWithNode.Position.X - childSize.Width) / 2);
}
}
if (alignBottomWithPanel)
{
if (alignTopWithPanel)
{
childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2);
}
else if (node.AlignTopWithNode == null)
{
childPos = childPos.WithY(_arrangeSize.Height - childSize.Height);
}
else
{
childPos = childPos.WithY((_arrangeSize.Height + node.AlignTopWithNode.Position.Y - childSize.Height) / 2);
}
}
child.Arrange(new Rect(childPos.X, childPos.Y, childSize.Width, childSize.Height));
node.Position = childPos;
node.Arranged = true;
}
}
}
}

9
src/Avalonia.Controls/ResolveByNameAttribute.cs

@ -0,0 +1,9 @@
using System;
namespace Avalonia.Controls
{
public class ResolveByNameAttribute : Attribute
{
}
}

31
src/Avalonia.Controls/TextBlock.cs

@ -411,9 +411,30 @@ namespace Avalonia.Controls
context.FillRectangle(background, new Rect(Bounds.Size));
}
if (TextLayout is null)
{
return;
}
var textAlignment = TextAlignment;
var width = Bounds.Size.Width;
var offsetX = 0.0;
switch (textAlignment)
{
case TextAlignment.Center:
offsetX = (width - TextLayout.Size.Width) / 2;
break;
case TextAlignment.Right:
offsetX = width - TextLayout.Size.Width;
break;
}
var padding = Padding;
TextLayout?.Draw(context, new Point(padding.Left, padding.Top));
TextLayout.Draw(context, new Point(padding.Left + offsetX, padding.Top));
}
/// <summary>
@ -431,7 +452,7 @@ namespace Avalonia.Controls
return new TextLayout(
text ?? string.Empty,
FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
FontSize,
Foreground,
TextAlignment,
@ -470,12 +491,12 @@ namespace Avalonia.Controls
if (_constraint != availableSize)
{
_constraint = availableSize;
InvalidateTextLayout();
}
_constraint = availableSize;
var measuredSize = TextLayout?.Bounds.Size ?? Size.Empty;
var measuredSize = TextLayout?.Size ?? Size.Empty;
return measuredSize.Inflate(padding);
}

10
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -54,7 +54,7 @@ namespace Avalonia.Headless
class HeadlessCursorFactoryStub : IStandardCursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType)
{
return new PlatformHandle(new IntPtr((int)cursorType), "STUB");
@ -101,7 +101,7 @@ namespace Avalonia.Headless
public bool IsFixedPitch => true;
public void Dispose()
{
{
}
public ushort GetGlyph(uint codepoint)
@ -155,9 +155,9 @@ namespace Avalonia.Headless
return new List<string> { "Arial" };
}
public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
{
fontKey = new FontKey("Arial", fontWeight, fontStyle);
fontKey = new FontKey("Arial", fontStyle, fontWeight);
return true;
}
}
@ -169,7 +169,7 @@ namespace Avalonia.Headless
{
public void Save(Stream outputStream)
{
}
}
public IWindowIconImpl LoadIcon(string fileName)

14
src/Avalonia.Visuals/Media/FontManager.cs

@ -79,12 +79,13 @@ namespace Avalonia.Media
/// Returns a new typeface, or an existing one if a matching typeface exists.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <returns>
/// The typeface.
/// </returns>
public Typeface GetOrAddTypeface(FontFamily fontFamily, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
public Typeface GetOrAddTypeface(FontFamily fontFamily, FontStyle fontStyle = FontStyle.Normal,
FontWeight fontWeight = FontWeight.Normal)
{
while (true)
{
@ -93,7 +94,7 @@ namespace Avalonia.Media
fontFamily = _defaultFontFamily;
}
var key = new FontKey(fontFamily.Name, fontWeight, fontStyle);
var key = new FontKey(fontFamily.Name, fontStyle, fontWeight);
if (_typefaceCache.TryGetValue(key, out var typeface))
{
@ -121,15 +122,16 @@ namespace Avalonia.Media
/// Returns <c>null</c> if no fallback was found.
/// </summary>
/// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param>
/// <returns>
/// The matched typeface.
/// </returns>
public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = FontWeight.Normal,
public Typeface MatchCharacter(int codepoint,
FontStyle fontStyle = FontStyle.Normal,
FontWeight fontWeight = FontWeight.Normal,
FontFamily fontFamily = null, CultureInfo culture = null)
{
foreach (var cachedTypeface in _typefaceCache.Values)
@ -142,7 +144,7 @@ namespace Avalonia.Media
}
}
var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontWeight, fontStyle, fontFamily, culture, out var key) ?
var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out var key) ?
_typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Style, key.Weight)) :
null;

2
src/Avalonia.Visuals/Media/Fonts/FontKey.cs

@ -4,7 +4,7 @@ namespace Avalonia.Media.Fonts
{
public readonly struct FontKey : IEquatable<FontKey>
{
public FontKey(string familyName, FontWeight weight, FontStyle style)
public FontKey(string familyName, FontStyle style, FontWeight weight)
{
FamilyName = familyName;
Style = style;

11
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -89,16 +89,7 @@ namespace Avalonia.Media.TextFormatting
/// <returns>The split result.</returns>
public SplitTextCharactersResult Split(int length)
{
var glyphCount = 0;
var firstCharacters = GlyphRun.Characters.Take(length);
var codepointEnumerator = new CodepointEnumerator(firstCharacters);
while (codepointEnumerator.MoveNext())
{
glyphCount++;
}
var glyphCount = GlyphRun.FindGlyphIndex(GlyphRun.Characters.Start + length);
if (GlyphRun.Characters.Length == length)
{

2
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@ -71,7 +71,7 @@ namespace Avalonia.Media.TextFormatting
//ToDo: Fix FontFamily fallback
currentTypeface =
FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Weight, defaultTypeface.Style, defaultTypeface.FontFamily);
FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.FontFamily);
if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
{

69
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -72,6 +72,11 @@ namespace Avalonia.Media.TextFormatting
{
foreach (var shapedCharacters in previousLineBreak.RemainingCharacters)
{
if (shapedCharacters == null)
{
continue;
}
textRuns.Add(shapedCharacters);
if (TryGetLineBreak(shapedCharacters, out var runLineBreak))
@ -106,7 +111,7 @@ namespace Avalonia.Media.TextFormatting
var glyphRun = TextShaper.Current.ShapeText(run.Text, run.Properties.Typeface,
run.Properties.FontRenderingEmSize, run.Properties.CultureInfo);
var shapedCharacters = new ShapedTextCharacters(glyphRun, textRun.Properties);
var shapedCharacters = new ShapedTextCharacters(glyphRun, run.Properties);
textRuns.Add(shapedCharacters);
}
@ -355,9 +360,67 @@ namespace Avalonia.Media.TextFormatting
{
var glyphRun = textCharacters.GlyphRun;
var characterHit = glyphRun.GetCharacterHitFromDistance(availableWidth, out _);
if (glyphRun.Bounds.Width < availableWidth)
{
return glyphRun.Characters.Length;
}
var glyphCount = 0;
var currentWidth = 0.0;
if (glyphRun.GlyphAdvances.IsEmpty)
{
var glyphTypeface = glyphRun.GlyphTypeface;
for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
{
var glyph = glyphRun.GlyphIndices[i];
var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
if (currentWidth + advance > availableWidth)
{
break;
}
currentWidth += advance;
glyphCount++;
}
}
else
{
for (var i = 0; i < glyphRun.GlyphAdvances.Length; i++)
{
var advance = glyphRun.GlyphAdvances[i];
if (currentWidth + advance > availableWidth)
{
break;
}
currentWidth += advance;
glyphCount++;
}
}
if (glyphCount == glyphRun.GlyphIndices.Length)
{
return glyphRun.Characters.Length;
}
if (glyphRun.GlyphClusters.IsEmpty)
{
return glyphCount;
}
var firstCluster = glyphRun.GlyphClusters[0];
var lastCluster = glyphRun.GlyphClusters[glyphCount];
return characterHit.FirstCharacterIndex + characterHit.TrailingLength - textCharacters.Text.Start;
return lastCluster - firstCluster;
}
/// <summary>

37
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@ -103,12 +103,12 @@ namespace Avalonia.Media.TextFormatting
public IReadOnlyList<TextLine> TextLines { get; private set; }
/// <summary>
/// Gets the bounds of the layout.
/// Gets the size of the layout.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public Rect Bounds { get; private set; }
public Size Size { get; private set; }
/// <summary>
/// Draws the text layout.
@ -126,7 +126,10 @@ namespace Avalonia.Media.TextFormatting
foreach (var textLine in TextLines)
{
textLine.Draw(context, new Point(origin.X, currentY));
var offsetX = TextLine.GetParagraphOffsetX(textLine.LineMetrics.Size.Width, Size.Width,
_paragraphProperties.TextAlignment);
textLine.Draw(context, new Point(origin.X + offsetX, currentY));
currentY += textLine.LineMetrics.Size.Height;
}
@ -158,22 +161,16 @@ namespace Avalonia.Media.TextFormatting
/// Updates the current bounds.
/// </summary>
/// <param name="textLine">The text line.</param>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <param name="bottom">The bottom.</param>
private static void UpdateBounds(TextLine textLine, ref double left, ref double right, ref double bottom)
/// <param name="width">The current width.</param>
/// <param name="height">The current height.</param>
private static void UpdateBounds(TextLine textLine, ref double width, ref double height)
{
if (right < textLine.LineMetrics.BaselineOrigin.X + textLine.LineMetrics.Size.Width)
{
right = textLine.LineMetrics.BaselineOrigin.X + textLine.LineMetrics.Size.Width;
}
if (left < textLine.LineMetrics.BaselineOrigin.X)
if (width < textLine.LineMetrics.Size.Width)
{
left = textLine.LineMetrics.BaselineOrigin.X;
width = textLine.LineMetrics.Size.Width;
}
bottom += textLine.LineMetrics.Size.Height;
height += textLine.LineMetrics.Size.Height;
}
/// <summary>
@ -204,13 +201,13 @@ namespace Avalonia.Media.TextFormatting
TextLines = new List<TextLine> { textLine };
Bounds = new Rect(textLine.LineMetrics.BaselineOrigin.X, 0, 0, textLine.LineMetrics.Size.Height);
Size = new Size(0, textLine.LineMetrics.Size.Height);
}
else
{
var textLines = new List<TextLine>();
double left = 0.0, right = 0.0, bottom = 0.0;
double width = 0.0, height = 0.0;
var currentPosition = 0;
@ -228,9 +225,9 @@ namespace Avalonia.Media.TextFormatting
textLines.Add(textLine);
UpdateBounds(textLine, ref left, ref right, ref bottom);
UpdateBounds(textLine, ref width, ref height);
if (!double.IsPositiveInfinity(MaxHeight) && bottom > MaxHeight)
if (!double.IsPositiveInfinity(MaxHeight) && height > MaxHeight)
{
break;
}
@ -247,7 +244,7 @@ namespace Avalonia.Media.TextFormatting
textLines.Add(emptyTextLine);
}
Bounds = new Rect(left, 0, right, bottom);
Size = new Size(width, height);
TextLines = textLines;
}

3
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -33,8 +33,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in _textRuns)
{
var baselineOrigin = new Point(currentX + LineMetrics.BaselineOrigin.X,
origin.Y + LineMetrics.BaselineOrigin.Y);
var baselineOrigin = new Point(currentX, origin.Y + LineMetrics.TextBaseline);
textRun.Draw(drawingContext, baselineOrigin);

17
src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs

@ -9,10 +9,10 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public readonly struct TextLineMetrics
{
public TextLineMetrics(Size size, Point baselineOrigin, TextRange textRange)
public TextLineMetrics(Size size, double textBaseline, TextRange textRange)
{
Size = size;
BaselineOrigin = baselineOrigin;
TextBaseline = textBaseline;
TextRange = textRange;
}
@ -33,12 +33,9 @@ namespace Avalonia.Media.TextFormatting
public Size Size { get; }
/// <summary>
/// Gets the baseline origin.
/// Gets the distance from the top to the baseline of the line of text.
/// </summary>
/// <value>
/// The baseline origin.
/// </value>
public Point BaselineOrigin { get; }
public double TextBaseline { get; }
/// <summary>
/// Creates the text line metrics.
@ -81,16 +78,12 @@ namespace Avalonia.Media.TextFormatting
}
}
var xOrigin = TextLine.GetParagraphOffsetX(lineWidth, paragraphWidth, paragraphProperties.TextAlignment);
var baselineOrigin = new Point(xOrigin, -ascent);
var size = new Size(lineWidth,
double.IsNaN(paragraphProperties.LineHeight) || MathUtilities.IsZero(paragraphProperties.LineHeight) ?
descent - ascent + lineGap :
paragraphProperties.LineHeight);
return new TextLineMetrics(size, baselineOrigin, textRange);
return new TextLineMetrics(size, -ascent, textRange);
}
}
}

5
src/Avalonia.Visuals/Platform/IFontManagerImpl.cs

@ -22,15 +22,16 @@ namespace Avalonia.Platform
/// Tries to match a specified character to a typeface that supports specified font properties.
/// </summary>
/// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param>
/// <param name="fontKey">The matching font key.</param>
/// <returns>
/// <c>True</c>, if the <see cref="IFontManagerImpl"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns>
bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle,
bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey);
/// <summary>

2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -25,6 +25,7 @@
<Compile Include="MarkupExtensions\CompiledBindings\PropertyInfoAccessorPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\TaskStreamPlugin.cs" />
<Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
<Compile Include="MarkupExtensions\ResolveByNameExtension.cs" />
<Compile Include="MarkupExtensions\ResourceInclude.cs" />
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
<Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
@ -57,6 +58,7 @@
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlCompiledBindingsMetadataRemover.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlMetadataRemover.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlPropertyPathTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlRootObjectScopeTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformInstanceAttachedProperties.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs" />

36
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs

@ -0,0 +1,36 @@
using System;
using Avalonia.Controls;
using Avalonia.Data.Core;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class ResolveByNameExtension
{
public ResolveByNameExtension(string name)
{
Name = name;
}
public string Name { get; }
public object? ProvideValue(IServiceProvider serviceProvider)
{
var nameScope = serviceProvider.GetService<INameScope>();
var value = nameScope.FindAsync(Name);
if(value.IsCompleted)
return value.GetResult();
var provideValueTarget = serviceProvider.GetService<IProvideValueTarget>();
var target = provideValueTarget.TargetObject;
if (provideValueTarget.TargetProperty is IPropertyInfo property)
value.OnCompleted(() => property.Set(target, value.GetResult()));
return null;
}
}
}

11
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -40,22 +40,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
// Targeted
InsertBefore<PropertyReferenceResolver>(
new AvaloniaXamlIlTransformInstanceAttachedProperties(),
new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
InsertAfter<PropertyReferenceResolver>(new AvaloniaXamlIlAvaloniaPropertyResolver());
InsertAfter<PropertyReferenceResolver>(
new AvaloniaXamlIlAvaloniaPropertyResolver());
InsertBefore<ContentConvertTransformer>(
InsertBefore<ContentConvertTransformer>(
new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlPropertyPathTransformer(),
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer()
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
);
// After everything else

49
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs

@ -0,0 +1,49 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
#nullable enable
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlResolveByNameMarkupExtensionReplacer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlAstXamlPropertyValueNode propertyValueNode)) return node;
if (!(propertyValueNode.Property is XamlAstClrProperty clrProperty)) return node;
IEnumerable<IXamlCustomAttribute> attributes = propertyValueNode.Property.GetClrProperty().CustomAttributes;
if (propertyValueNode.Property is XamlAstClrProperty referenceNode &&
referenceNode.Getter != null)
{
attributes = attributes.Concat(referenceNode.Getter.CustomAttributes);
}
if (attributes.All(attribute => attribute.Type.FullName != "Avalonia.Controls.ResolveByNameAttribute"))
return node;
if (propertyValueNode.Values.Count != 1 || !(propertyValueNode.Values.First() is XamlAstTextNode))
return node;
var newNode = new XamlAstObjectNode(
propertyValueNode.Values[0],
new XamlAstClrTypeReference(propertyValueNode.Values[0],
context.GetAvaloniaTypes().ResolveByNameExtension, true))
{
Arguments = new List<IXamlAstValueNode> { propertyValueNode.Values[0] }
};
if (XamlTransformHelpers.TryConvertMarkupExtension(context, newNode, out var extensionNode))
{
propertyValueNode.Values[0] = extensionNode;
}
return node;
}
}
}

3
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs

@ -174,6 +174,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
other is GetterMethod m && m.Name == Name && m.DeclaringType.Equals(DeclaringType);
public IXamlType ReturnType => Parent.PropertyType;
public IReadOnlyList<IXamlType> Parameters { get; }
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => DeclaringType.CustomAttributes;
public void EmitCall(IXamlILEmitter emitter)
{
var method = Parent._avaloniaObject

4
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -37,6 +37,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType CompiledBindingPathBuilder { get; }
public IXamlType CompiledBindingPath { get; }
public IXamlType CompiledBindingExtension { get; }
public IXamlType ResolveByNameExtension { get; }
public IXamlType DataTemplate { get; }
public IXamlType IItemsPresenterHost { get; }
public IXamlType ReflectionBindingExtension { get; }
@ -92,6 +95,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
CompiledBindingPathBuilder = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPathBuilder");
CompiledBindingPath = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPath");
CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");
ResolveByNameExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ResolveByNameExtension");
DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost");
ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension");

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@ -1 +1 @@
Subproject commit 0028377ce7c7dc21f9fe71b45f62a95991b1ab58
Subproject commit 7b8b3013bd42e1992838a525c991f44191da55be

7
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -29,7 +29,8 @@ namespace Avalonia.Skia
[ThreadStatic] private static string[] t_languageTagBuffer;
public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle,
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
{
SKFontStyle skFontStyle;
@ -80,7 +81,7 @@ namespace Avalonia.Skia
continue;
}
fontKey = new FontKey(skTypeface.FamilyName, fontWeight, fontStyle);
fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight);
return true;
}
@ -91,7 +92,7 @@ namespace Avalonia.Skia
if (skTypeface != null)
{
fontKey = new FontKey(skTypeface.FamilyName, fontWeight, fontStyle);
fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight);
return true;
}

5
src/Skia/Avalonia.Skia/GlyphRunImpl.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Platform;
using JetBrains.Annotations;
using SkiaSharp;
namespace Avalonia.Skia
@ -7,9 +8,9 @@ namespace Avalonia.Skia
/// <inheritdoc />
public class GlyphRunImpl : IGlyphRunImpl
{
public GlyphRunImpl(SKTextBlob textBlob)
public GlyphRunImpl([NotNull] SKTextBlob textBlob)
{
TextBlob = textBlob;
TextBlob = textBlob ?? throw new ArgumentNullException (nameof (textBlob));
}
/// <summary>

49
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -19,42 +19,49 @@ namespace Avalonia.Skia
public SKTypeface Get(Typeface typeface)
{
var key = new FontKey(typeface.FontFamily.Name, typeface.Weight, typeface.Style);
var key = new FontKey(typeface.FontFamily.Name, typeface.Style, typeface.Weight);
return GetNearestMatch(_typefaces, key);
}
private static SKTypeface GetNearestMatch(IDictionary<FontKey, SKTypeface> typefaces, FontKey key)
{
if (typefaces.ContainsKey(key))
if (typefaces.TryGetValue(new FontKey(key.FamilyName, key.Style, key.Weight), out var typeface))
{
return typefaces[key];
return typeface;
}
var keys = typefaces.Keys.Where(
x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && x.Style == key.Style).ToArray();
var weight = (int)key.Weight;
if (!keys.Any())
{
keys = typefaces.Keys.Where(
x => x.Weight == key.Weight && (x.Style >= key.Style || x.Style < key.Style)).ToArray();
weight -= weight % 100; // make sure we start at a full weight
if (!keys.Any())
for (var i = (int)key.Style; i < 2; i++)
{
// only try 2 font weights in each direction
for (var j = 0; j < 200; j += 100)
{
keys = typefaces.Keys.Where(
x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) &&
(x.Style >= key.Style || x.Style < key.Style)).ToArray();
}
}
if (weight - j >= 100)
{
if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight - j)), out typeface))
{
return typeface;
}
}
if (keys.Length == 0)
{
return null;
}
if (weight + j > 900)
{
continue;
}
key = keys[0];
if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight + j)), out typeface))
{
return typeface;
}
}
}
return typefaces[key];
//Nothing was found so we use the first typeface we can get.
return typefaces.Values.FirstOrDefault();
}
}
}

8
src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs

@ -43,18 +43,20 @@ namespace Avalonia.Skia
{
var assetStream = assetLoader.Open(asset);
if (assetStream == null) throw new InvalidOperationException("Asset could not be loaded.");
if (assetStream == null)
throw new InvalidOperationException("Asset could not be loaded.");
var typeface = SKTypeface.FromStream(assetStream);
if(typeface == null) throw new InvalidOperationException("Typeface could not be loaded.");
if (typeface == null)
throw new InvalidOperationException("Typeface could not be loaded.");
if (typeface.FamilyName != fontFamily.Name)
{
continue;
}
var key = new FontKey(fontFamily.Name, (FontWeight)typeface.FontWeight, (FontStyle)typeface.FontSlant);
var key = new FontKey(fontFamily.Name, (FontStyle)typeface.FontSlant, (FontWeight)typeface.FontWeight);
typeFaceCollection.AddTypeface(key, typeface);
}

93
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -15,51 +15,7 @@ namespace Avalonia.Skia
{
using (var buffer = new Buffer())
{
buffer.ContentType = ContentType.Unicode;
var breakCharPosition = text.Length - 1;
var codepoint = Codepoint.ReadAt(text, breakCharPosition, out var count);
if (codepoint.IsBreakChar)
{
var breakCharCount = 1;
if (text.Length > 1)
{
var previousCodepoint = Codepoint.ReadAt(text, breakCharPosition - count, out _);
if (codepoint == '\r' && previousCodepoint == '\n'
|| codepoint == '\n' && previousCodepoint == '\r')
{
breakCharCount = 2;
}
}
if (breakCharPosition != text.Start)
{
buffer.AddUtf16(text.Buffer.Span.Slice(0, text.Length - breakCharCount));
}
var cluster = buffer.GlyphInfos.Length > 0 ?
buffer.GlyphInfos[buffer.Length - 1].Cluster + 1 :
(uint)text.Start;
switch (breakCharCount)
{
case 1:
buffer.Add('\u200C', cluster);
break;
case 2:
buffer.Add('\u200C', cluster);
buffer.Add('\u200D', cluster);
break;
}
}
else
{
buffer.AddUtf16(text.Buffer.Span);
}
FillBuffer(buffer, text);
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
@ -93,7 +49,7 @@ namespace Avalonia.Skia
{
glyphIndices[i] = (ushort)glyphInfos[i].Codepoint;
clusters[i] = (ushort)(text.Start + glyphInfos[i].Cluster);
clusters[i] = (ushort)glyphInfos[i].Cluster;
if (!glyphTypeface.IsFixedPitch)
{
@ -112,6 +68,51 @@ namespace Avalonia.Skia
}
}
private static void FillBuffer(Buffer buffer, ReadOnlySlice<char> text)
{
buffer.ContentType = ContentType.Unicode;
var i = 0;
while (i < text.Length)
{
var codepoint = Codepoint.ReadAt(text, i, out var count);
var cluster = (uint)(text.Start + i);
if (codepoint.IsBreakChar)
{
if (i < text.End)
{
var nextCodepoint = Codepoint.ReadAt(text, i + 1, out _);
if (nextCodepoint == '\r' && codepoint == '\n' || nextCodepoint == '\n' && codepoint == '\r')
{
count++;
buffer.Add('\u200C', cluster);
buffer.Add('\u200D', cluster);
}
else
{
buffer.Add('\u200C', cluster);
}
}
else
{
buffer.Add('\u200C', cluster);
}
}
else
{
buffer.Add(codepoint, cluster);
}
i += count;
}
}
private static void SetOffset(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale,
ref Vector[] offsetBuffer)
{

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

@ -32,7 +32,8 @@ namespace Avalonia.Direct2D1.Media
return fontFamilies;
}
public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle,
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
{
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
@ -50,7 +51,7 @@ namespace Avalonia.Direct2D1.Media
var fontFamilyName = font.FontFamily.FamilyNames.GetString(0);
fontKey = new FontKey(fontFamilyName, fontWeight, fontStyle);
fontKey = new FontKey(fontFamilyName, fontStyle, fontWeight);
return true;
}

59
tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs

@ -0,0 +1,59 @@
using Avalonia.Controls.Shapes;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class RelativePanelTests
{
[Fact]
public void Lays_Out_1_Child_Next_the_other()
{
var rect1 = new Rectangle { Height = 20, Width = 20 };
var rect2 = new Rectangle { Height = 20, Width = 20 };
var target = new RelativePanel
{
VerticalAlignment = Layout.VerticalAlignment.Top,
HorizontalAlignment = Layout.HorizontalAlignment.Left,
Children =
{
rect1, rect2
}
};
RelativePanel.SetAlignLeftWithPanel(rect1 , true);
RelativePanel.SetRightOf(rect2, rect1);
target.Measure(new Size(400, 400));
target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Size(40, 20), target.Bounds.Size);
Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds);
Assert.Equal(new Rect(20, 0, 20, 20), target.Children[1].Bounds);
}
public void Lays_Out_1_Child_Below_the_other()
{
var rect1 = new Rectangle { Height = 20, Width = 20 };
var rect2 = new Rectangle { Height = 20, Width = 20 };
var target = new RelativePanel
{
VerticalAlignment = Layout.VerticalAlignment.Top,
HorizontalAlignment = Layout.HorizontalAlignment.Left,
Children =
{
rect1, rect2
}
};
RelativePanel.SetAlignLeftWithPanel(rect1, true);
RelativePanel.SetBelow(rect2, rect1);
target.Measure(new Size(400, 400));
target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Size(20, 40), target.Bounds.Size);
Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds);
Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds);
}
}
}

2
tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs

@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
var r = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return r.CreateFormattedText(text,
FontManager.Current.GetOrAddTypeface(fontFamily, fontWeight, fontStyle),
FontManager.Current.GetOrAddTypeface(fontFamily, fontStyle, fontWeight),
fontSize,
textAlignment,
wrapping,

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

@ -38,7 +38,7 @@ namespace Avalonia.Skia.UnitTests.Media
private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle, FontFamily fontFamily,
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out FontKey fontKey)
{
foreach (var customTypeface in _customTypefaces)
@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media
continue;
}
fontKey = new FontKey(customTypeface.FontFamily.Name, fontWeight, fontStyle);
fontKey = new FontKey(customTypeface.FontFamily.Name, fontStyle, fontWeight);
return true;
}
@ -56,7 +56,7 @@ namespace Avalonia.Skia.UnitTests.Media
var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
fontKey = new FontKey(fallback?.FamilyName ?? _defaultFamilyName, fontWeight, fontStyle);
fontKey = new FontKey(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
return true;
}

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

@ -260,6 +260,39 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_Not_Produce_TextLine_Wider_Than_ParagraphWidth()
{
using (Start())
{
const string text =
"Multiline TextBlock with TextWrapping.\r\rLorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. " +
"Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. " +
"Vivamus pretium ornare est.";
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.Wrap);
var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl();
var textSourceIndex = 0;
while (textSourceIndex < text.Length)
{
var textLine =
formatter.FormatLine(textSource, textSourceIndex, 200, paragraphProperties);
Assert.True(textLine.LineMetrics.Size.Width <= 200);
textSourceIndex += textLine.TextRange.Length;
}
}
}
public static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

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

@ -512,7 +512,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(numberOfLines, layout.TextLines.Count);
Assert.Equal(numberOfLines * lineHeight, layout.Bounds.Height);
Assert.Equal(numberOfLines * lineHeight, layout.Size.Height);
}
}

2
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

@ -25,7 +25,7 @@ namespace Avalonia.UnitTests
return new[] { _defaultFamilyName };
}
public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle, FontFamily fontFamily,
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out FontKey fontKey)
{
fontKey = default;

1
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@ -34,6 +34,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Background = Brushes.Red,
Child = textBlock = new TextBlock
{
TextWrapping = TextWrapping.NoWrap,
Text = "Hello World",
}
}

37
tests/Avalonia.Visuals.UnitTests/Utilities/ReadOnlySpanTests.cs

@ -0,0 +1,37 @@
using System.Linq;
using Avalonia.Utilities;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Utilities
{
public class ReadOnlySpanTests
{
[Fact]
public void Should_Skip()
{
var buffer = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var slice = new ReadOnlySlice<int>(buffer);
var skipped = slice.Skip(2);
var expected = buffer.Skip(2);
Assert.Equal(expected, skipped);
}
[Fact]
public void Should_Take()
{
var buffer = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var slice = new ReadOnlySlice<int>(buffer);
var taken = slice.Take(8);
var expected = buffer.Take(8);
Assert.Equal(expected, taken);
}
}
}
Loading…
Cancel
Save