Browse Source

Merge branch 'master' of https://github.com/AvaloniaUI/Avalonia into compiled-bindings

pull/2734/head
Jeremy Koritzinsky 6 years ago
parent
commit
c9d3274154
  1. 2
      .gitmodules
  2. 2
      nukebuild/Build.cs
  3. 76
      nukebuild/BuildTasksPatcher.cs
  4. 3
      nukebuild/_build.csproj
  5. 69
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  6. 1
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  7. 1
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  8. 27
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  9. 2
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  10. 45
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  11. 31
      src/Avalonia.Controls/ComboBox.cs
  12. 1
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  13. 15
      src/Avalonia.Controls/Primitives/AccessText.cs
  14. 35
      src/Avalonia.Controls/TextBlock.cs
  15. 4
      src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs
  16. 3
      src/Avalonia.Desktop/Avalonia.Desktop.csproj
  17. 40
      src/Avalonia.Themes.Default/ComboBox.xaml
  18. 199
      src/Avalonia.Themes.Fluent/ComboBox.xaml
  19. 70
      src/Avalonia.Themes.Fluent/ComboBoxItem.xaml
  20. 4
      src/Avalonia.Visuals/Media/FontManager.cs
  21. 112
      src/Avalonia.Visuals/Media/GlyphRun.cs
  22. 182
      src/Avalonia.Visuals/Media/TextDecoration.cs
  23. 17
      src/Avalonia.Visuals/Media/TextDecorationCollection.cs
  24. 2
      src/Avalonia.Visuals/Media/TextDecorationUnit.cs
  25. 6
      src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs
  26. 69
      src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs
  27. 40
      src/Avalonia.Visuals/Media/TextFormatting/GenericTextRunProperties.cs
  28. 23
      src/Avalonia.Visuals/Media/TextFormatting/ShapeableTextCharacters.cs
  29. 164
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  30. 212
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextRun.cs
  31. 395
      src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs
  32. 259
      src/Avalonia.Visuals/Media/TextFormatting/SimpleTextLine.cs
  33. 181
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  34. 71
      src/Avalonia.Visuals/Media/TextFormatting/TextFormat.cs
  35. 148
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs
  36. 544
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  37. 192
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  38. 17
      src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
  39. 17
      src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs
  40. 235
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  41. 69
      src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
  42. 33
      src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs
  43. 16
      src/Avalonia.Visuals/Media/TextFormatting/TextRange.cs
  44. 17
      src/Avalonia.Visuals/Media/TextFormatting/TextRun.cs
  45. 90
      src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs
  46. 8
      src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs
  47. 39
      src/Avalonia.Visuals/Media/TextFormatting/TextStyle.cs
  48. 24
      src/Avalonia.Visuals/Media/TextFormatting/TextStyleRun.cs
  49. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
  50. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/CodepointEnumerator.cs
  51. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Grapheme.cs
  52. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeEnumerator.cs
  53. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  54. 9
      src/Avalonia.Visuals/Media/TextWrapping.cs
  55. 12
      src/Avalonia.Visuals/Media/Typeface.cs
  56. 12
      src/Avalonia.Visuals/Platform/ITextShaperImpl.cs
  57. 5
      src/Avalonia.Visuals/Utilities/ReadOnlySlice.cs
  58. 30
      src/Avalonia.Visuals/Utilities/ValueSpan.cs
  59. 6
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  60. 34
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  61. 81
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  62. 16
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
  63. 78
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  64. 96
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
  65. 30
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
  66. 10
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
  67. 114
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  68. 18
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  69. 8
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
  70. 28
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
  71. 28
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
  72. 58
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  73. 26
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
  74. 8
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
  75. 96
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
  76. 45
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
  77. 118
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  78. 74
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  79. 78
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
  80. 18
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs
  81. 12
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
  82. 80
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  83. 14
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
  84. 19
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs
  85. 111
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  86. 190
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs
  87. 32
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs
  88. 30
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs
  89. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  90. 2
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  91. 14
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  92. 15
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
  93. 4
      tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs
  94. 12
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  95. 45
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
  96. 2
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  97. 8
      tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs
  98. 4
      tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs
  99. 38
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/FormattableTextSource.cs
  100. 36
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs

2
.gitmodules

@ -3,4 +3,4 @@
url = https://github.com/kekekeks/Numerge.git
[submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"]
path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
url = https://github.com/kekekeks/XamlIl.git
url = https://github.com/kekekeks/XamlX.git

2
nukebuild/Build.cs

@ -268,6 +268,8 @@ partial class Build : NukeBuild
.DependsOn(CreateIntermediateNugetPackages)
.Executes(() =>
{
BuildTasksPatcher.PatchBuildTasksInPackage(Parameters.NugetIntermediateRoot / "Avalonia.Build.Tasks." +
Parameters.Version + ".nupkg");
var config = Numerge.MergeConfiguration.LoadFile(RootDirectory / "nukebuild" / "numerge.config");
EnsureCleanDirectory(Parameters.NugetRoot);
if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,

76
nukebuild/BuildTasksPatcher.cs

@ -0,0 +1,76 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using ILRepacking;
using Mono.Cecil;
public class BuildTasksPatcher
{
public static void PatchBuildTasksInPackage(string packagePath)
{
using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite),
ZipArchiveMode.Update))
{
foreach (var entry in archive.Entries.ToList())
{
if (entry.Name == "Avalonia.Build.Tasks.dll")
{
var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".dll");
var output = temp + ".output";
var patched = new MemoryStream();
try
{
entry.ExtractToFile(temp, true);
var repack = new ILRepacking.ILRepack(new RepackOptions()
{
Internalize = true,
InputAssemblies = new[]
{
temp, typeof(Mono.Cecil.AssemblyDefinition).Assembly.GetModules()[0]
.FullyQualifiedName
},
SearchDirectories = new string[0],
OutputFile = output
});
repack.Repack();
// 'hurr-durr assembly with the same name is already loaded' prevention
using (var asm = AssemblyDefinition.ReadAssembly(output,
new ReaderParameters { ReadWrite = true, InMemory = true, }))
{
asm.Name = new AssemblyNameDefinition(
"Avalonia.Build.Tasks."
+ Guid.NewGuid().ToString().Replace("-", ""),
new Version(0, 0, 0));
asm.Write(patched);
patched.Position = 0;
}
}
finally
{
try
{
if (File.Exists(temp))
File.Delete(temp);
if (File.Exists(output))
File.Delete(output);
}
catch
{
//ignore
}
}
var fn = entry.FullName;
entry.Delete();
var newEntry = archive.CreateEntry(fn, CompressionLevel.Optimal);
using (var s = newEntry.Open())
patched.CopyTo(s);
}
}
}
}
}

3
nukebuild/_build.csproj

@ -14,6 +14,9 @@
<PackageReference Include="xunit.runner.console" Version="2.3.1" />
<PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
<PackageReference Include="ILRepack.NETStandard" Version="2.0.4" />
<!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" />
</ItemGroup>
<ItemGroup>

69
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -64,51 +64,42 @@
<TextDecorationCollection>
<TextDecoration
Location="Overline"
PenThicknessUnit="Pixel">
<TextDecoration.Pen>
<Pen Thickness="2">
<Pen.Brush>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Green"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Pen.Brush>
</Pen>
</TextDecoration.Pen>
StrokeThicknessUnit="Pixel"
StrokeThickness="2">
<TextDecoration.Stroke>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Green"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextDecoration.Stroke>
</TextDecoration>
<TextDecoration
Location="Strikethrough"
PenThicknessUnit="Pixel">
<TextDecoration.Pen>
<Pen Thickness="1">
<Pen.Brush>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Pen.Brush>
</Pen>
</TextDecoration.Pen>
StrokeThicknessUnit="Pixel"
StrokeThickness="1">
<TextDecoration.Stroke>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextDecoration.Stroke>
</TextDecoration>
<TextDecoration
Location="Underline"
PenThicknessUnit="Pixel">
<TextDecoration.Pen>
<Pen Thickness="2">
<Pen.Brush>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="1" Color="Red"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Pen.Brush>
</Pen>
</TextDecoration.Pen>
StrokeThicknessUnit="Pixel"
StrokeThickness="2">
<TextDecoration.Stroke>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="1" Color="Red"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextDecoration.Stroke>
</TextDecoration>
</TextDecorationCollection>
</TextBlock.TextDecorations>

1
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
<ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001" />

1
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -89,6 +89,7 @@ namespace Avalonia.Data.Core
return x;
}
}
return null;
}
protected override void StopListeningCore()

27
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -5,7 +5,7 @@
<OutputType>exe</OutputType>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
<DefineConstants>$(DefineConstants);BUILDTASK;XAMLIL_CECIL_INTERNAL;XAMLIL_INTERNAL</DefineConstants>
<DefineConstants>$(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL</DefineConstants>
<CopyLocalLockFileAssemblies Condition="$(TargetFramework) == 'netstandard2.0'">true</CopyLocalLockFileAssemblies>
<NoWarn>NU1605</NoWarn>
</PropertyGroup>
@ -21,10 +21,10 @@
<Link>XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Remove="external/cecil/**/*.*" />
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\**\*.cs">
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX\**\*.cs">
<Link>XamlIl/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs">
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX.IL.Cecil\**\*.cs">
<Link>XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs">
@ -58,27 +58,8 @@
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\**\obj\**\*.cs" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\TypeSystem\SreTypeSystem.cs" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
<PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" />
<PackageReference Condition="$(TargetFramework) == 'netstandard2.0'" Include="ILRepack.MSBuild.Task" Version="2.0.13" PrivateAssets="All" />
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
</ItemGroup>
<Target Name="ILRepack" AfterTargets="Build" Condition="$(TargetFramework) == 'netstandard2.0'">
<PropertyGroup>
<WorkingDirectory>$(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)</WorkingDirectory>
</PropertyGroup>
<ItemGroup>
<InputAssemblies Include="Mono.Cecil.dll" />
</ItemGroup>
<ILRepack OutputType="$(OutputType)" MainAssembly="$(AssemblyName).dll" OutputAssembly="$(AssemblyName).dll" InputAssemblies="@(InputAssemblies)" WorkingDirectory="$(WorkingDirectory)" />
<ItemGroup>
<DeleteNonNeededResults Include="$(WorkingDirectory)\*.dll" />
<DeleteNonNeededResults Remove="$(WorkingDirectory)\Avalonia.Build.Tasks.dll" />
</ItemGroup>
<Delete Files="@(DeleteNonNeededResults)" />
</Target>
</Project>

2
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs

@ -5,7 +5,7 @@ using Avalonia.Utilities;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using XamlIl.TypeSystem;
using XamlX.TypeSystem;
namespace Avalonia.Build.Tasks
{

45
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -7,17 +7,18 @@ using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Microsoft.Build.Framework;
using Mono.Cecil;
using XamlIl.TypeSystem;
using Avalonia.Utilities;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Parsers;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Parsers;
using XamlX.Transform;
using XamlX.TypeSystem;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using TypeAttributes = Mono.Cecil.TypeAttributes;
using XamlX.IL;
namespace Avalonia.Build.Tasks
{
@ -58,11 +59,11 @@ namespace Avalonia.Build.Tasks
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(indexerAccessorClosure);
var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem);
var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)));
@ -72,10 +73,10 @@ namespace Avalonia.Build.Tasks
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(contextDef);
var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
xamlLanguage);
var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
xamlLanguage, emitConfig);
var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass) { EnableIlVerification = verifyIl };
var compiler = new AvaloniaXamlIlCompiler(compilerConfig, emitConfig, contextClass) { EnableIlVerification = verifyIl };
var editorBrowsableAttribute = typeSystem
.GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute"))
@ -135,35 +136,35 @@ namespace Avalonia.Build.Tasks
// StreamReader is needed here to handle BOM
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
var parsed = XDocumentXamlIlParser.Parse(xaml);
var parsed = XDocumentXamlParser.Parse(xaml);
var initialRoot = (XamlIlAstObjectNode)parsed.Root;
var initialRoot = (XamlAstObjectNode)parsed.Root;
var precompileDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>()
var precompileDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile");
if (precompileDirective != null)
{
var precompileText = (precompileDirective.Values[0] as XamlIlAstTextNode)?.Text.Trim()
var precompileText = (precompileDirective.Values[0] as XamlAstTextNode)?.Text.Trim()
.ToLowerInvariant();
if (precompileText == "false")
continue;
if (precompileText != "true")
throw new XamlIlParseException("Invalid value for x:Precompile", precompileDirective);
throw new XamlParseException("Invalid value for x:Precompile", precompileDirective);
}
var classDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>()
var classDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
IXamlIlType classType = null;
IXamlType classType = null;
if (classDirective != null)
{
if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlIlAstTextNode tn))
throw new XamlIlParseException("x:Class should have a string value", classDirective);
if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlAstTextNode tn))
throw new XamlParseException("x:Class should have a string value", classDirective);
classType = typeSystem.TargetAssembly.FindType(tn.Text);
if (classType == null)
throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective);
throw new XamlParseException($"Unable to find type `{tn.Text}`", classDirective);
compiler.OverrideRootType(parsed,
new XamlIlAstClrTypeReference(classDirective, classType, false));
new XamlAstClrTypeReference(classDirective, classType, false));
initialRoot.Children.Remove(classDirective);
}
@ -332,7 +333,7 @@ namespace Avalonia.Build.Tasks
catch (Exception e)
{
int lineNumber = 0, linePosition = 0;
if (e is XamlIlParseException xe)
if (e is XamlParseException xe)
{
lineNumber = xe.LineNumber;
linePosition = xe.LinePosition;

31
src/Avalonia.Controls/ComboBox.cs

@ -7,6 +7,7 @@ using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.VisualTree;
@ -63,6 +64,18 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush> PlaceholderForegroundProperty =
AvaloniaProperty.Register<ComboBox, IBrush>(nameof(PlaceholderForeground));
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
ContentControl.HorizontalContentAlignmentProperty.AddOwner<ComboBox>();
/// <summary>
/// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
private bool _isDropDownOpen;
private Popup _popup;
private object _selectionBoxItem;
@ -133,6 +146,24 @@ namespace Avalonia.Controls
set { SetValue(VirtualizationModeProperty, value); }
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
get { return GetValue(HorizontalContentAlignmentProperty); }
set { SetValue(HorizontalContentAlignmentProperty, value); }
}
/// <summary>
/// Gets or sets the vertical alignment of the content within the control.
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
get { return GetValue(VerticalContentAlignmentProperty); }
set { SetValue(VerticalContentAlignmentProperty, value); }
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{

1
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -95,6 +95,7 @@ namespace Avalonia.Controls.Presenters
static ContentPresenter()
{
AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsArrange<ContentPresenter>(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty);
AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
ContentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));

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

@ -110,7 +110,7 @@ namespace Avalonia.Controls.Primitives
foreach (var textLine in TextLayout.TextLines)
{
if (textLine.Text.End < textPosition)
if (textLine.TextRange.End < textPosition)
{
currentY += textLine.LineMetrics.Size.Height;
@ -121,21 +121,22 @@ namespace Avalonia.Controls.Primitives
foreach (var textRun in textLine.TextRuns)
{
if (!(textRun is ShapedTextRun shapedRun))
if (!(textRun is ShapedTextCharacters shapedTextCharacters))
{
continue;
}
if (shapedRun.GlyphRun.Characters.End < textPosition)
if (shapedTextCharacters.GlyphRun.Characters.End < textPosition)
{
currentX += shapedRun.GlyphRun.Bounds.Width;
currentX += shapedTextCharacters.GlyphRun.Bounds.Width;
continue;
}
var characterHit = shapedRun.GlyphRun.FindNearestCharacterHit(textPosition, out var width);
var characterHit =
shapedTextCharacters.GlyphRun.FindNearestCharacterHit(textPosition, out var width);
var distance = shapedRun.GlyphRun.GetDistanceFromCharacterHit(characterHit);
var distance = shapedTextCharacters.GlyphRun.GetDistanceFromCharacterHit(characterHit);
currentX += distance - width;
@ -144,7 +145,7 @@ namespace Avalonia.Controls.Primitives
width = 0.0;
}
return new Rect(currentX, currentY, width, shapedRun.GlyphRun.Bounds.Height);
return new Rect(currentX, currentY, width, shapedTextCharacters.GlyphRun.Bounds.Height);
}
}

35
src/Avalonia.Controls/TextBlock.cs

@ -70,6 +70,15 @@ namespace Avalonia.Controls
Brushes.Black,
inherits: true);
/// <summary>
/// Defines the <see cref="LineHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> LineHeightProperty =
AvaloniaProperty.Register<TextBlock, double>(
nameof(LineHeight),
double.NaN,
validate: IsValidLineHeight);
/// <summary>
/// Defines the <see cref="MaxLines"/> property.
/// </summary>
@ -122,19 +131,19 @@ namespace Avalonia.Controls
{
ClipToBoundsProperty.OverrideDefaultValue<TextBlock>(true);
AffectsRender<TextBlock>(BackgroundProperty, ForegroundProperty,
AffectsRender<TextBlock>(BackgroundProperty, ForegroundProperty,
TextAlignmentProperty, TextDecorationsProperty);
AffectsMeasure<TextBlock>(FontSizeProperty, FontWeightProperty,
FontStyleProperty, TextWrappingProperty, FontFamilyProperty,
TextTrimmingProperty, TextProperty, PaddingProperty);
AffectsMeasure<TextBlock>(FontSizeProperty, FontWeightProperty,
FontStyleProperty, TextWrappingProperty, FontFamilyProperty,
TextTrimmingProperty, TextProperty, PaddingProperty, LineHeightProperty, MaxLinesProperty);
Observable.Merge(TextProperty.Changed, ForegroundProperty.Changed,
TextAlignmentProperty.Changed, TextWrappingProperty.Changed,
TextTrimmingProperty.Changed, FontSizeProperty.Changed,
FontStyleProperty.Changed, FontWeightProperty.Changed,
FontFamilyProperty.Changed, TextDecorationsProperty.Changed,
PaddingProperty.Changed
PaddingProperty.Changed, MaxLinesProperty.Changed, LineHeightProperty.Changed
).AddClassHandler<TextBlock>((x, _) => x.InvalidateTextLayout());
}
@ -230,6 +239,15 @@ namespace Avalonia.Controls
set { SetValue(ForegroundProperty, value); }
}
/// <summary>
/// Gets or sets the height of each line of content.
/// </summary>
public double LineHeight
{
get => GetValue(LineHeightProperty);
set => SetValue(LineHeightProperty, value);
}
/// <summary>
/// Gets or sets the maximum number of text lines.
/// </summary>
@ -395,7 +413,7 @@ namespace Avalonia.Controls
var padding = Padding;
TextLayout?.Draw(context.PlatformImpl, new Point(padding.Left, padding.Top));
TextLayout?.Draw(context, new Point(padding.Left, padding.Top));
}
/// <summary>
@ -422,7 +440,8 @@ namespace Avalonia.Controls
TextDecorations,
constraint.Width,
constraint.Height,
MaxLines);
maxLines: MaxLines,
lineHeight: LineHeight);
}
/// <summary>
@ -471,5 +490,7 @@ namespace Avalonia.Controls
}
private static bool IsValidMaxLines(int maxLines) => maxLines >= 0;
private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0;
}
}

4
src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs

@ -46,10 +46,6 @@ namespace Avalonia
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseX11();
static void LoadDirect2D1<TAppBuilder>(TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseDirect2D1();
static void LoadSkia<TAppBuilder>(TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseSkia();

3
src/Avalonia.Desktop/Avalonia.Desktop.csproj

@ -5,8 +5,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/Windows/Avalonia.Win32/Avalonia.Win32.csproj" />
<ProjectReference Include="../../src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj" />
<ProjectReference Include="../../src/Windows/Avalonia.Win32/Avalonia.Win32.csproj" />
<ProjectReference Include="../../src/Skia/Avalonia.Skia/Avalonia.Skia.csproj" />
<ProjectReference Include="../../src/Avalonia.Native/Avalonia.Native.csproj" />
<ProjectReference Include="../../packages/Avalonia/Avalonia.csproj" />

40
src/Avalonia.Themes.Default/ComboBox.xaml

@ -1,10 +1,29 @@
<Styles xmlns="https://github.com/avaloniaui">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="10">
<ComboBox PlaceholderText="Select an item">
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
</ComboBox>
<ComboBox IsEnabled="False"
Width="200"
SelectedIndex="1"
HorizontalContentAlignment="Center">
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
</ComboBox>
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="ComboBox">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="4" />
<Setter Property="MinHeight" Value="20" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="border"
@ -15,8 +34,8 @@
<ContentControl Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding ItemTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="Left"
VerticalAlignment="Center"/>
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
<ToggleButton Name="toggle"
BorderThickness="0"
Background="Transparent"
@ -30,7 +49,7 @@
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z"/>
Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z" />
</ToggleButton>
<Popup Name="PART_Popup"
IsOpen="{TemplateBinding IsDropDownOpen, Mode=TwoWay}"
@ -45,8 +64,7 @@
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
VirtualizationMode="{TemplateBinding VirtualizationMode}"
/>
VirtualizationMode="{TemplateBinding VirtualizationMode}" />
</ScrollViewer>
</Border>
</Popup>
@ -56,7 +74,7 @@
</Setter>
</Style>
<Style Selector="ComboBox:pointerover /template/ Border#border">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" />
</Style>
<Style Selector="ComboBox:disabled /template/ Border#border">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />

199
src/Avalonia.Themes.Fluent/ComboBox.xaml

@ -1,4 +1,5 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="10">
@ -6,8 +7,10 @@
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
</ComboBox>
<ComboBox IsEnabled="False">
<ComboBox IsEnabled="False"
Width="200"
SelectedIndex="1"
HorizontalContentAlignment="Center">
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
</ComboBox>
@ -25,6 +28,7 @@
</Styles.Resources>
<Style Selector="ComboBox">
<Setter Property="Padding" Value="{DynamicResource ComboBoxPadding}" />
<Setter Property="FocusAdorner" Value="{x:Null}" />
<Setter Property="MaxDropDownHeight" Value="504" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForeground}" />
<Setter Property="Background" Value="{DynamicResource ComboBoxBackground}" />
@ -32,6 +36,8 @@
<Setter Property="BorderThickness" Value="{DynamicResource ComboBoxBorderThemeThickness}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
@ -39,70 +45,76 @@
<Setter Property="PlaceholderForeground" Value="{DynamicResource ComboBoxPlaceHolderForeground}" />
<Setter Property="Template">
<ControlTemplate>
<Grid RowDefinitions="Auto, *, Auto" ColumnDefinitions="*,32">
<Grid RowDefinitions="Auto, *, Auto"
ColumnDefinitions="*,32">
<ContentPresenter x:Name="HeaderContentPresenter"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
TextBlock.FontWeight="{DynamicResource ComboBoxHeaderThemeFontWeight}"
Margin="{DynamicResource ComboBoxTopHeaderMargin}"
VerticalAlignment="Top" />
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
TextBlock.FontWeight="{DynamicResource ComboBoxHeaderThemeFontWeight}"
Margin="{DynamicResource ComboBoxTopHeaderMargin}"
VerticalAlignment="Top" />
<Border x:Name="Background"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}"
MinWidth="{DynamicResource ComboBoxThemeMinWidth}" />
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}"
MinWidth="{DynamicResource ComboBoxThemeMinWidth}" />
<Border x:Name="HighlightBackground"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Background="{DynamicResource ComboBoxBackgroundUnfocused}"
BorderBrush="{DynamicResource ComboBoxBackgroundBorderBrushUnfocused}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Opacity="0" />
<TextBlock x:Name="PlaceholderTextBlock"
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="{TemplateBinding Padding}"
Foreground="{TemplateBinding PlaceholderForeground}"
Text="{TemplateBinding PlaceholderText}" IsVisible="{TemplateBinding SelectionBoxItem, Converter={x:Static ObjectConverters.IsNull}}"/>
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Background="{DynamicResource ComboBoxBackgroundUnfocused}"
BorderBrush="{DynamicResource ComboBoxBackgroundBorderBrushUnfocused}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}" />
<TextBlock x:Name="PlaceholderTextBlock"
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
Text="{TemplateBinding PlaceholderText}"
IsVisible="{TemplateBinding SelectionBoxItem, Converter={x:Static ObjectConverters.IsNull}}" />
<ContentControl x:Name="ContentPresenter"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding ItemTemplate}"
Grid.Row="1"
Grid.Column="0"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="Left"
VerticalAlignment="Center">
</ContentControl>
Grid.Row="1"
Grid.Column="0"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
<Border x:Name="DropDownOverlay"
Grid.Row="1"
Grid.Column="1"
Background="Transparent"
Margin="0,1,1,1"
Width="30" IsVisible="False"
HorizontalAlignment="Right" />
Grid.Row="1"
Grid.Column="1"
Background="Transparent"
Margin="0,1,1,1"
Width="30"
IsVisible="False"
HorizontalAlignment="Right" />
<Viewbox UseLayoutRounding="False"
MinHeight="{DynamicResource ComboBoxMinHeight}"
Grid.Row="1"
Grid.Column="1"
IsHitTestVisible="False"
Margin="0,0,10,0" Height="12" Width="12"
HorizontalAlignment="Right"
VerticalAlignment="Center">
MinHeight="{DynamicResource ComboBoxMinHeight}"
Grid.Row="1"
Grid.Column="1"
IsHitTestVisible="False"
Margin="0,0,10,0"
Height="12"
Width="12"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<Panel>
<Panel Height="12" Width="12" />
<Path x:Name="DropDownGlyph" Stretch="Uniform" VerticalAlignment="Center" Data="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" />
<Panel Height="12"
Width="12" />
<Path x:Name="DropDownGlyph"
Stretch="Uniform"
VerticalAlignment="Center"
Data="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" />
</Panel>
</Viewbox>
<Popup Name="PART_Popup"
@ -114,20 +126,19 @@
StaysOpen="False">
<Border x:Name="PopupBorder"
Background="{DynamicResource ComboBoxDropDownBackground}"
BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}"
BorderThickness="{DynamicResource ComboBoxDropdownBorderThickness}"
Margin="0,-1,0,-1"
Padding="{DynamicResource ComboBoxDropdownBorderPadding}"
HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}">
BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}"
BorderThickness="{DynamicResource ComboBoxDropdownBorderThickness}"
Margin="0,-1,0,-1"
Padding="{DynamicResource ComboBoxDropdownBorderPadding}"
HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}">
<ScrollViewer>
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
Margin="{DynamicResource ComboBoxDropdownContentMargin}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
VirtualizationMode="{TemplateBinding VirtualizationMode}"
/>
VirtualizationMode="{TemplateBinding VirtualizationMode}" />
</ScrollViewer>
</Border>
</Popup>
@ -136,57 +147,81 @@
</Setter>
</Style>
<!-- NormalState -->
<!-- NormalState -->
<Style Selector="ComboBox /template/ TextBlock#PlaceholderTextBlock">
<Setter Property="Foreground" Value="{DynamicResource ComboBoxPlaceHolderForeground}"/>
<Setter Property="Foreground" Value="{DynamicResource ComboBoxPlaceHolderForeground}" />
</Style>
<Style Selector="ComboBox /template/ Border#HighlightBackground">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="ComboBox /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForeground}"/>
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForeground}" />
</Style>
<!-- PointerOver State -->
<!-- PointerOver State -->
<Style Selector="ComboBox:pointerover /template/ Border#Background">
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundPointerOver}"/>
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxBorderBrushPointerOver}" />
</Style>
<!-- Pressed State -->
<!-- Pressed State -->
<Style Selector="ComboBox:pressed /template/ Border#Background">
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundPressed}"/>
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxBorderBrushPressed}" />
</Style>
<!-- Disabled State -->
<!-- Disabled State -->
<Style Selector="ComboBox:disabled /template/ Border#Background">
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundDisabled}"/>
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxBorderBrushDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ ContentControl#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ TextBlock#PlaceholderTextBlock">
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}"/>
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ TextBlock#PlaceholderTextBlock">
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}"/>
<Style Selector="ComboBox:disabled /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundDisabled}"/>
<!-- Focused State -->
<Style Selector="ComboBox:focus-visible /template/ Border#HighlightBackground">
<Setter Property="IsVisible" Value="True" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxBackgroundBorderBrushFocused}" />
</Style>
<!-- Focused State -->
<Style Selector="ComboBox:focus-visible /template/ ContentControl#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
</Style>
<!-- Focus Pressed State -->
<Style Selector="ComboBox:focus-visible /template/ TextBlock#PlaceholderTextBlock">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
</Style>
<!-- Unfocused State -->
<Style Selector="ComboBox:focus-visible /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocused}" />
</Style>
<!-- Focus Pressed State -->
<Style Selector="ComboBox:focused:pressed /template/ ContentControl#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundFocusedPressed}" />
</Style>
<Style Selector="ComboBox:focused:pressed /template/ TextBlock#PlaceholderTextBlock">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxPlaceHolderForegroundFocusedPressed}" />
</Style>
<Style Selector="ComboBox:focused:pressed /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocusedPressed}" />
</Style>
</Styles>

70
src/Avalonia.Themes.Fluent/ComboBoxItem.xaml

@ -1,17 +1,19 @@
<Styles xmlns="https://github.com/avaloniaui">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="10">
<ComboBox>
<Border Background="{DynamicResource ComboBoxDropDownBackground}"
BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}"
BorderThickness="{DynamicResource ComboBoxDropdownBorderThickness}"
Margin="0,-1,0,-1"
Padding="{DynamicResource ComboBoxDropdownBorderPadding}"
CornerRadius="{DynamicResource OverlayCornerRadius}">
<StackPanel HorizontalAlignment="Stretch">
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
</ComboBox>
<ComboBox IsEnabled="False">
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
</ComboBox>
</StackPanel>
<ComboBoxItem>Item 2 long</ComboBoxItem>
<ComboBoxItem IsSelected="True">Item 3</ComboBoxItem>
<ComboBoxItem IsEnabled="False">Item 4</ComboBoxItem>
</StackPanel>
</Border>
</Border>
</Design.PreviewWith>
@ -19,12 +21,11 @@
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForeground}" />
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackground}" />
<Setter Property="Padding" Value="{DynamicResource ComboBoxItemThemePadding}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemRevealBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ComboBoxItemRevealBorderThemeThickness}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
TextBlock.Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
@ -32,28 +33,57 @@
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}"/>
Padding="{TemplateBinding Padding}" />
</ControlTemplate>
</Setter>
</Style>
<!-- PointerOver state -->
<Style Selector="ComboBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundPointerOver}" />
</Style>
<!-- Disabled state -->
<Style Selector="ComboBoxItem:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundDisabled}" />
</Style>
<!-- Pressed state -->
<Style Selector="ComboBoxItem:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundPressed}" />
</Style>
<!-- Selected state -->
<Style Selector="ComboBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelected}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelected}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelected}" />
</Style>
<Style Selector="ComboBoxItem:selected:focus /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
<!-- Selected Disabled state -->
<Style Selector="ComboBoxItem:selected:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelectedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelectedDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedDisabled}" />
</Style>
<!-- Selected PointerOver state -->
<Style Selector="ComboBoxItem:selected:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelectedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPointerOver}" />
</Style>
<Style Selector="ComboBoxItem:selected:focus:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
<!-- Selected Pressed state -->
<Style Selector="ComboBoxItem:selected:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelectedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPressed}" />
</Style>
</Styles>

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

@ -100,7 +100,7 @@ namespace Avalonia.Media
return typeface;
}
typeface = new Typeface(fontFamily, fontWeight, fontStyle);
typeface = new Typeface(fontFamily, fontStyle, fontWeight);
if (_typefaceCache.TryAdd(key, typeface))
{
@ -143,7 +143,7 @@ namespace Avalonia.Media
}
var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontWeight, fontStyle, fontFamily, culture, out var key) ?
_typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Weight, key.Style)) :
_typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Style, key.Weight)) :
null;
return matchedTypeface;

112
src/Avalonia.Visuals/Media/GlyphRun.cs

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Utility;
using Avalonia.Utilities;
namespace Avalonia.Media
{
@ -205,13 +205,16 @@ namespace Avalonia.Media
var glyphIndex = FindGlyphIndex(characterHit.FirstCharacterIndex);
var currentCluster = _glyphClusters[glyphIndex];
if (characterHit.TrailingLength > 0)
if (!GlyphClusters.IsEmpty)
{
while (glyphIndex < _glyphClusters.Length && _glyphClusters[glyphIndex] == currentCluster)
var currentCluster = GlyphClusters[glyphIndex];
if (characterHit.TrailingLength > 0)
{
glyphIndex++;
while (glyphIndex < GlyphClusters.Length && GlyphClusters[glyphIndex] == currentCluster)
{
glyphIndex++;
}
}
}
@ -302,7 +305,7 @@ namespace Avalonia.Media
}
}
var characterHit = FindNearestCharacterHit(GlyphClusters[index], out var width);
var characterHit = FindNearestCharacterHit(GlyphClusters.IsEmpty ? index : GlyphClusters[index], out var width);
var offset = GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex));
@ -370,26 +373,31 @@ namespace Avalonia.Media
/// </returns>
public int FindGlyphIndex(int characterIndex)
{
if (GlyphClusters.IsEmpty)
{
return characterIndex;
}
if (IsLeftToRight)
{
if (characterIndex < _glyphClusters[0])
if (characterIndex < GlyphClusters[0])
{
return 0;
}
if (characterIndex > _glyphClusters[_glyphClusters.Length - 1])
if (characterIndex > GlyphClusters[GlyphClusters.Length - 1])
{
return _glyphClusters.End;
}
}
else
{
if (characterIndex < _glyphClusters[_glyphClusters.Length - 1])
if (characterIndex < GlyphClusters[GlyphClusters.Length - 1])
{
return _glyphClusters.End;
}
if (characterIndex > _glyphClusters[0])
if (characterIndex > GlyphClusters[0])
{
return 0;
}
@ -397,7 +405,7 @@ namespace Avalonia.Media
var comparer = IsLeftToRight ? s_ascendingComparer : s_descendingComparer;
var clusters = _glyphClusters.Buffer.Span;
var clusters = GlyphClusters.Buffer.Span;
// Find the start of the cluster at the character index.
var start = clusters.BinarySearch((ushort)characterIndex, comparer);
@ -418,9 +426,19 @@ namespace Avalonia.Media
}
}
while (start > 0 && clusters[start - 1] == clusters[start])
if (IsLeftToRight)
{
start--;
while (start > 0 && clusters[start - 1] == clusters[start])
{
start--;
}
}
else
{
while (start + 1 < clusters.Length && clusters[start + 1] == clusters[start])
{
start++;
}
}
return start;
@ -440,34 +458,74 @@ namespace Avalonia.Media
var start = FindGlyphIndex(index);
var currentCluster = _glyphClusters[start];
if (GlyphClusters.IsEmpty)
{
width = GetGlyphWidth(index);
return new CharacterHit(start, 1);
}
var trailingLength = 0;
var cluster = GlyphClusters[start];
while (start < _glyphClusters.Length && _glyphClusters[start] == currentCluster)
var nextCluster = cluster;
var currentIndex = start;
while (nextCluster == cluster)
{
if (GlyphAdvances.IsEmpty)
width += GetGlyphWidth(currentIndex);
if (IsLeftToRight)
{
var glyph = GlyphIndices[start];
currentIndex++;
width += GlyphTypeface.GetGlyphAdvance(glyph) * Scale;
if (currentIndex == GlyphClusters.Length)
{
break;
}
}
else
{
width += GlyphAdvances[start];
currentIndex--;
if (currentIndex < 0)
{
break;
}
}
trailingLength++;
start++;
nextCluster = GlyphClusters[currentIndex];
}
int trailingLength;
if (nextCluster == cluster)
{
trailingLength = Characters.Start + Characters.Length - cluster;
}
else
{
trailingLength = nextCluster - cluster;
}
if (start == _glyphClusters.Length &&
currentCluster + trailingLength != Characters.Start + Characters.Length)
return new CharacterHit(cluster, trailingLength);
}
/// <summary>
/// Gets a glyph's width.
/// </summary>
/// <param name="index">The glyph index.</param>
/// <returns>The glyph's width.</returns>
private double GetGlyphWidth(int index)
{
if (GlyphAdvances.IsEmpty)
{
trailingLength = Characters.Start + Characters.Length - currentCluster;
var glyph = GlyphIndices[index];
return GlyphTypeface.GetGlyphAdvance(glyph) * Scale;
}
return new CharacterHit(currentCluster, trailingLength);
return GlyphAdvances[index];
}
/// <summary>

182
src/Avalonia.Visuals/Media/TextDecoration.cs

@ -1,4 +1,5 @@
using Avalonia.Media.Immutable;
using Avalonia.Collections;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Media
{
@ -14,28 +15,52 @@ namespace Avalonia.Media
AvaloniaProperty.Register<TextDecoration, TextDecorationLocation>(nameof(Location));
/// <summary>
/// Defines the <see cref="Pen"/> property.
/// Defines the <see cref="Stroke"/> property.
/// </summary>
public static readonly StyledProperty<IPen> PenProperty =
AvaloniaProperty.Register<TextDecoration, IPen>(nameof(Pen));
public static readonly StyledProperty<IBrush> StrokeProperty =
AvaloniaProperty.Register<TextDecoration, IBrush>(nameof(Stroke));
/// <summary>
/// Defines the <see cref="PenThicknessUnit"/> property.
/// Defines the <see cref="StrokeThicknessUnit"/> property.
/// </summary>
public static readonly StyledProperty<TextDecorationUnit> PenThicknessUnitProperty =
AvaloniaProperty.Register<TextDecoration, TextDecorationUnit>(nameof(PenThicknessUnit));
public static readonly StyledProperty<TextDecorationUnit> StrokeThicknessUnitProperty =
AvaloniaProperty.Register<TextDecoration, TextDecorationUnit>(nameof(StrokeThicknessUnit));
/// <summary>
/// Defines the <see cref="PenOffset"/> property.
/// Defines the <see cref="StrokeDashArray"/> property.
/// </summary>
public static readonly StyledProperty<double> PenOffsetProperty =
AvaloniaProperty.Register<TextDecoration, double>(nameof(PenOffset));
public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty =
AvaloniaProperty.Register<TextDecoration, AvaloniaList<double>>(nameof(StrokeDashArray));
/// <summary>
/// Defines the <see cref="PenOffsetUnit"/> property.
/// Defines the <see cref="StrokeDashOffset"/> property.
/// </summary>
public static readonly StyledProperty<TextDecorationUnit> PenOffsetUnitProperty =
AvaloniaProperty.Register<TextDecoration, TextDecorationUnit>(nameof(PenOffsetUnit));
public static readonly StyledProperty<double> StrokeDashOffsetProperty =
AvaloniaProperty.Register<TextDecoration, double>(nameof(StrokeDashOffset));
/// <summary>
/// Defines the <see cref="StrokeThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> StrokeThicknessProperty =
AvaloniaProperty.Register<TextDecoration, double>(nameof(StrokeThickness), 1);
/// <summary>
/// Defines the <see cref="StrokeLineCap"/> property.
/// </summary>
public static readonly StyledProperty<PenLineCap> StrokeLineCapProperty =
AvaloniaProperty.Register<TextDecoration, PenLineCap>(nameof(StrokeLineCap));
/// <summary>
/// Defines the <see cref="StrokeOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> StrokeOffsetProperty =
AvaloniaProperty.Register<TextDecoration, double>(nameof(StrokeOffset));
/// <summary>
/// Defines the <see cref="StrokeOffsetUnit"/> property.
/// </summary>
public static readonly StyledProperty<TextDecorationUnit> StrokeOffsetUnitProperty =
AvaloniaProperty.Register<TextDecoration, TextDecorationUnit>(nameof(StrokeOffsetUnit));
/// <summary>
/// Gets or sets the location.
@ -50,54 +75,139 @@ namespace Avalonia.Media
}
/// <summary>
/// Gets or sets the pen.
/// Gets or sets the <see cref="IBrush"/> that specifies how the <see cref="TextDecoration"/> is painted.
/// </summary>
/// <value>
/// The pen.
/// </value>
public IPen Pen
public IBrush Stroke
{
get { return GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
/// <summary>
/// Gets the units in which the thickness of the <see cref="TextDecoration"/> is expressed.
/// </summary>
public TextDecorationUnit StrokeThicknessUnit
{
get => GetValue(StrokeThicknessUnitProperty);
set => SetValue(StrokeThicknessUnitProperty, value);
}
/// <summary>
/// Gets or sets a collection of <see cref="double"/> values that indicate the pattern of dashes and gaps
/// that is used to draw the <see cref="TextDecoration"/>.
/// </summary>
public AvaloniaList<double> StrokeDashArray
{
get { return GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); }
}
/// <summary>
/// Gets or sets a value that specifies the distance within the dash pattern where a dash begins.
/// </summary>
public double StrokeDashOffset
{
get { return GetValue(StrokeDashOffsetProperty); }
set { SetValue(StrokeDashOffsetProperty, value); }
}
/// <summary>
/// Gets or sets the thickness of the <see cref="TextDecoration"/>.
/// </summary>
public double StrokeThickness
{
get => GetValue(PenProperty);
set => SetValue(PenProperty, value);
get { return GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
/// <summary>
/// Gets the units in which the Thickness of the text decoration's <see cref="Pen"/> is expressed.
/// Gets or sets a <see cref="PenLineCap"/> enumeration value that describes the shape at the ends of a line.
/// </summary>
public TextDecorationUnit PenThicknessUnit
public PenLineCap StrokeLineCap
{
get => GetValue(PenThicknessUnitProperty);
set => SetValue(PenThicknessUnitProperty, value);
get { return GetValue(StrokeLineCapProperty); }
set { SetValue(StrokeLineCapProperty, value); }
}
/// <summary>
/// Gets or sets the pen offset.
/// The stroke's offset.
/// </summary>
/// <value>
/// The pen offset.
/// </value>
public double PenOffset
public double StrokeOffset
{
get => GetValue(PenOffsetProperty);
set => SetValue(PenOffsetProperty, value);
get => GetValue(StrokeOffsetProperty);
set => SetValue(StrokeOffsetProperty, value);
}
/// <summary>
/// Gets the units in which the <see cref="PenOffset"/> value is expressed.
/// Gets the units in which the <see cref="StrokeOffset"/> value is expressed.
/// </summary>
public TextDecorationUnit PenOffsetUnit
public TextDecorationUnit StrokeOffsetUnit
{
get => GetValue(PenOffsetUnitProperty);
set => SetValue(PenOffsetUnitProperty, value);
get => GetValue(StrokeOffsetUnitProperty);
set => SetValue(StrokeOffsetUnitProperty, value);
}
/// <summary>
/// Creates an immutable clone of the <see cref="TextDecoration"/>.
/// Draws the <see cref="TextDecoration"/> at given origin.
/// </summary>
/// <returns>The immutable clone.</returns>
public ImmutableTextDecoration ToImmutable()
/// <param name="drawingContext">The drawing context.</param>
/// <param name="shapedTextCharacters">The shaped characters that are decorated.</param>
/// <param name="origin">The origin.</param>
internal void Draw(DrawingContext drawingContext, ShapedTextCharacters shapedTextCharacters, Point origin)
{
return new ImmutableTextDecoration(Location, Pen?.ToImmutable(), PenThicknessUnit, PenOffset, PenOffsetUnit);
var fontRenderingEmSize = shapedTextCharacters.Properties.FontRenderingEmSize;
var fontMetrics = shapedTextCharacters.FontMetrics;
var thickness = StrokeThickness;
switch (StrokeThicknessUnit)
{
case TextDecorationUnit.FontRecommended:
switch (Location)
{
case TextDecorationLocation.Underline:
thickness = fontMetrics.UnderlineThickness;
break;
case TextDecorationLocation.Strikethrough:
thickness = fontMetrics.StrikethroughThickness;
break;
}
break;
case TextDecorationUnit.FontRenderingEmSize:
thickness = fontRenderingEmSize * thickness;
break;
}
switch (Location)
{
case TextDecorationLocation.Overline:
origin += new Point(0, fontMetrics.Ascent);
break;
case TextDecorationLocation.Strikethrough:
origin += new Point(0, -fontMetrics.StrikethroughPosition);
break;
case TextDecorationLocation.Underline:
origin += new Point(0, -fontMetrics.UnderlinePosition);
break;
}
switch (StrokeOffsetUnit)
{
case TextDecorationUnit.FontRenderingEmSize:
origin += new Point(0, StrokeOffset * fontRenderingEmSize);
break;
case TextDecorationUnit.Pixel:
origin += new Point(0, StrokeOffset);
break;
}
var pen = new Pen(Stroke ?? shapedTextCharacters.Properties.ForegroundBrush, thickness,
new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap);
drawingContext.DrawLine(pen, origin, origin + new Point(shapedTextCharacters.Bounds.Width, 0));
}
}
}

17
src/Avalonia.Visuals/Media/TextDecorationCollection.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Media.Immutable;
using Avalonia.Utilities;
namespace Avalonia.Media
@ -11,22 +10,6 @@ namespace Avalonia.Media
/// </summary>
public class TextDecorationCollection : AvaloniaList<TextDecoration>
{
/// <summary>
/// Creates an immutable clone of the <see cref="TextDecorationCollection"/>.
/// </summary>
/// <returns>The immutable clone.</returns>
public ImmutableTextDecoration[] ToImmutable()
{
var immutable = new ImmutableTextDecoration[Count];
for (var i = 0; i < Count; i++)
{
immutable[i] = this[i].ToImmutable();
}
return immutable;
}
/// <summary>
/// Parses a <see cref="TextDecorationCollection"/> string.
/// </summary>

2
src/Avalonia.Visuals/Media/TextDecorationUnit.cs

@ -1,7 +1,7 @@
namespace Avalonia.Media
{
/// <summary>
/// Specifies the unit type of either a <see cref="TextDecoration.PenOffset"/> or a <see cref="Pen"/> thickness value.
/// Specifies the unit type of either a <see cref="TextDecoration.StrokeOffset"/> or a <see cref="TextDecoration.StrokeThickness"/> value.
/// </summary>
public enum TextDecorationUnit
{

6
src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs

@ -1,6 +1,4 @@
using Avalonia.Platform;
namespace Avalonia.Media.TextFormatting
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// A text run that supports drawing content.
@ -17,6 +15,6 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
/// <param name="drawingContext">The drawing context.</param>
/// <param name="origin">The origin.</param>
public abstract void Draw(IDrawingContextImpl drawingContext, Point origin);
public abstract void Draw(DrawingContext drawingContext, Point origin);
}
}

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

@ -0,0 +1,69 @@
namespace Avalonia.Media.TextFormatting
{
public class GenericTextParagraphProperties : TextParagraphProperties
{
private TextAlignment _textAlignment;
private TextWrapping _textWrapping;
private TextTrimming _textTrimming;
private double _lineHeight;
public GenericTextParagraphProperties(
TextRunProperties defaultTextRunProperties,
TextAlignment textAlignment = TextAlignment.Left,
TextWrapping textWrapping = TextWrapping.WrapWithOverflow,
TextTrimming textTrimming = TextTrimming.None,
double lineHeight = 0)
{
DefaultTextRunProperties = defaultTextRunProperties;
_textAlignment = textAlignment;
_textWrapping = textWrapping;
_textTrimming = textTrimming;
_lineHeight = lineHeight;
}
public override TextRunProperties DefaultTextRunProperties { get; }
public override TextAlignment TextAlignment => _textAlignment;
public override TextWrapping TextWrapping => _textWrapping;
public override TextTrimming TextTrimming => _textTrimming;
public override double LineHeight => _lineHeight;
/// <summary>
/// Set text alignment
/// </summary>
internal void SetTextAlignment(TextAlignment textAlignment)
{
_textAlignment = textAlignment;
}
/// <summary>
/// Set text wrap
/// </summary>
internal void SetTextWrapping(TextWrapping textWrapping)
{
_textWrapping = textWrapping;
}
/// <summary>
/// Set text trimming
/// </summary>
internal void SetTextTrimming(TextTrimming textTrimming)
{
_textTrimming = textTrimming;
}
/// <summary>
/// Set line height
/// </summary>
internal void SetLineHeight(double lineHeight)
{
_lineHeight = lineHeight;
}
}
}

40
src/Avalonia.Visuals/Media/TextFormatting/GenericTextRunProperties.cs

@ -0,0 +1,40 @@
using System.Globalization;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Generic implementation of TextRunProperties
/// </summary>
public class GenericTextRunProperties : TextRunProperties
{
public GenericTextRunProperties(Typeface typeface, double fontRenderingEmSize = 12,
TextDecorationCollection textDecorations = null, IBrush foregroundBrush = null, IBrush backgroundBrush = null,
CultureInfo cultureInfo = null)
{
Typeface = typeface;
FontRenderingEmSize = fontRenderingEmSize;
TextDecorations = textDecorations;
ForegroundBrush = foregroundBrush;
BackgroundBrush = backgroundBrush;
CultureInfo = cultureInfo;
}
/// <inheritdoc />
public override Typeface Typeface { get; }
/// <inheritdoc />
public override double FontRenderingEmSize { get; }
/// <inheritdoc />
public override TextDecorationCollection TextDecorations { get; }
/// <inheritdoc />
public override IBrush ForegroundBrush { get; }
/// <inheritdoc />
public override IBrush BackgroundBrush { get; }
/// <inheritdoc />
public override CultureInfo CultureInfo { get; }
}
}

23
src/Avalonia.Visuals/Media/TextFormatting/ShapeableTextCharacters.cs

@ -0,0 +1,23 @@
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// A group of characters that can be shaped.
/// </summary>
public sealed class ShapeableTextCharacters : TextRun
{
public ShapeableTextCharacters(ReadOnlySlice<char> text, TextRunProperties properties)
{
TextSourceLength = text.Length;
Text = text;
Properties = properties;
}
public override int TextSourceLength { get; }
public override ReadOnlySlice<char> Text { get; }
public override TextRunProperties Properties { get; }
}
}

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

@ -0,0 +1,164 @@
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// A text run that holds shaped characters.
/// </summary>
public sealed class ShapedTextCharacters : DrawableTextRun
{
public ShapedTextCharacters(GlyphRun glyphRun, TextRunProperties properties)
{
Text = glyphRun.Characters;
Properties = properties;
TextSourceLength = Text.Length;
FontMetrics = new FontMetrics(Properties.Typeface, Properties.FontRenderingEmSize);
GlyphRun = glyphRun;
}
/// <inheritdoc/>
public override ReadOnlySlice<char> Text { get; }
/// <inheritdoc/>
public override TextRunProperties Properties { get; }
/// <inheritdoc/>
public override int TextSourceLength { get; }
/// <inheritdoc/>
public override Rect Bounds => GlyphRun.Bounds;
/// <summary>
/// Gets the font metrics.
/// </summary>
/// <value>
/// The font metrics.
/// </value>
public FontMetrics FontMetrics { get; }
/// <summary>
/// Gets the glyph run.
/// </summary>
/// <value>
/// The glyphs.
/// </value>
public GlyphRun GlyphRun { get; }
/// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin)
{
if (GlyphRun.GlyphIndices.Length == 0)
{
return;
}
if (Properties.Typeface == null)
{
return;
}
if (Properties.ForegroundBrush == null)
{
return;
}
if (Properties.BackgroundBrush != null)
{
drawingContext.DrawRectangle(Properties.BackgroundBrush, null,
new Rect(origin.X, origin.Y + FontMetrics.Ascent, Bounds.Width, Bounds.Height));
}
drawingContext.DrawGlyphRun(Properties.ForegroundBrush, GlyphRun, origin);
if (Properties.TextDecorations == null)
{
return;
}
foreach (var textDecoration in Properties.TextDecorations)
{
textDecoration.Draw(drawingContext, this, origin);
}
}
/// <summary>
/// Splits the <see cref="TextRun"/> at specified length.
/// </summary>
/// <param name="length">The length.</param>
/// <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++;
}
if (GlyphRun.Characters.Length == length)
{
return new SplitTextCharactersResult(this, null);
}
if (GlyphRun.GlyphIndices.Length == glyphCount)
{
return new SplitTextCharactersResult(this, null);
}
var firstGlyphRun = new GlyphRun(
Properties.Typeface.GlyphTypeface,
Properties.FontRenderingEmSize,
GlyphRun.GlyphIndices.Take(glyphCount),
GlyphRun.GlyphAdvances.Take(glyphCount),
GlyphRun.GlyphOffsets.Take(glyphCount),
GlyphRun.Characters.Take(length),
GlyphRun.GlyphClusters.Take(glyphCount));
var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties);
var secondGlyphRun = new GlyphRun(
Properties.Typeface.GlyphTypeface,
Properties.FontRenderingEmSize,
GlyphRun.GlyphIndices.Skip(glyphCount),
GlyphRun.GlyphAdvances.Skip(glyphCount),
GlyphRun.GlyphOffsets.Skip(glyphCount),
GlyphRun.Characters.Skip(length),
GlyphRun.GlyphClusters.Skip(glyphCount));
var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties);
return new SplitTextCharactersResult(firstTextRun, secondTextRun);
}
public readonly struct SplitTextCharactersResult
{
public SplitTextCharactersResult(ShapedTextCharacters first, ShapedTextCharacters second)
{
First = first;
Second = second;
}
/// <summary>
/// Gets the first text run.
/// </summary>
/// <value>
/// The first text run.
/// </value>
public ShapedTextCharacters First { get; }
/// <summary>
/// Gets the second text run.
/// </summary>
/// <value>
/// The second text run.
/// </value>
public ShapedTextCharacters Second { get; }
}
}
}

212
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextRun.cs

@ -1,212 +0,0 @@
using Avalonia.Media.Immutable;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utility;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// A text run that holds a shaped glyph run.
/// </summary>
public sealed class ShapedTextRun : DrawableTextRun
{
public ShapedTextRun(ReadOnlySlice<char> text, TextStyle style) : this(
TextShaper.Current.ShapeText(text, style.TextFormat), style)
{
}
public ShapedTextRun(GlyphRun glyphRun, TextStyle style)
{
Text = glyphRun.Characters;
Style = style;
GlyphRun = glyphRun;
}
/// <inheritdoc/>
public override Rect Bounds => GlyphRun.Bounds;
/// <summary>
/// Gets the glyph run.
/// </summary>
/// <value>
/// The glyphs.
/// </value>
public GlyphRun GlyphRun { get; }
/// <inheritdoc/>
public override void Draw(IDrawingContextImpl drawingContext, Point origin)
{
if (GlyphRun.GlyphIndices.Length == 0)
{
return;
}
if (Style.TextFormat.Typeface == null)
{
return;
}
if (Style.Foreground == null)
{
return;
}
drawingContext.DrawGlyphRun(Style.Foreground, GlyphRun, origin);
if (Style.TextDecorations == null)
{
return;
}
foreach (var textDecoration in Style.TextDecorations)
{
DrawTextDecoration(drawingContext, textDecoration, origin);
}
}
/// <summary>
/// Draws the <see cref="TextDecoration"/> at given origin.
/// </summary>
/// <param name="drawingContext">The drawing context.</param>
/// <param name="textDecoration">The text decoration.</param>
/// <param name="origin">The origin.</param>
private void DrawTextDecoration(IDrawingContextImpl drawingContext, ImmutableTextDecoration textDecoration, Point origin)
{
var textFormat = Style.TextFormat;
var fontMetrics = Style.TextFormat.FontMetrics;
var thickness = textDecoration.Pen?.Thickness ?? 1.0;
switch (textDecoration.PenThicknessUnit)
{
case TextDecorationUnit.FontRecommended:
switch (textDecoration.Location)
{
case TextDecorationLocation.Underline:
thickness = fontMetrics.UnderlineThickness;
break;
case TextDecorationLocation.Strikethrough:
thickness = fontMetrics.StrikethroughThickness;
break;
}
break;
case TextDecorationUnit.FontRenderingEmSize:
thickness = textFormat.FontRenderingEmSize * thickness;
break;
}
switch (textDecoration.Location)
{
case TextDecorationLocation.Overline:
origin += new Point(0, textFormat.FontMetrics.Ascent);
break;
case TextDecorationLocation.Strikethrough:
origin += new Point(0, -textFormat.FontMetrics.StrikethroughPosition);
break;
case TextDecorationLocation.Underline:
origin += new Point(0, -textFormat.FontMetrics.UnderlinePosition);
break;
}
switch (textDecoration.PenOffsetUnit)
{
case TextDecorationUnit.FontRenderingEmSize:
origin += new Point(0, textDecoration.PenOffset * textFormat.FontRenderingEmSize);
break;
case TextDecorationUnit.Pixel:
origin += new Point(0, textDecoration.PenOffset);
break;
}
var pen = new ImmutablePen(
textDecoration.Pen?.Brush ?? Style.Foreground.ToImmutable(),
thickness,
textDecoration.Pen?.DashStyle?.ToImmutable(),
textDecoration.Pen?.LineCap ?? default,
textDecoration.Pen?.LineJoin ?? PenLineJoin.Miter,
textDecoration.Pen?.MiterLimit ?? 10.0);
drawingContext.DrawLine(pen, origin, origin + new Point(GlyphRun.Bounds.Width, 0));
}
/// <summary>
/// Splits the <see cref="TextRun"/> at specified length.
/// </summary>
/// <param name="length">The length.</param>
/// <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++;
}
if (GlyphRun.Characters.Length == length)
{
return new SplitTextCharactersResult(this, null);
}
if (GlyphRun.GlyphIndices.Length == glyphCount)
{
return new SplitTextCharactersResult(this, null);
}
var firstGlyphRun = new GlyphRun(
Style.TextFormat.Typeface.GlyphTypeface,
Style.TextFormat.FontRenderingEmSize,
GlyphRun.GlyphIndices.Take(glyphCount),
GlyphRun.GlyphAdvances.Take(glyphCount),
GlyphRun.GlyphOffsets.Take(glyphCount),
GlyphRun.Characters.Take(length),
GlyphRun.GlyphClusters.Take(length));
var firstTextRun = new ShapedTextRun(firstGlyphRun, Style);
var secondGlyphRun = new GlyphRun(
Style.TextFormat.Typeface.GlyphTypeface,
Style.TextFormat.FontRenderingEmSize,
GlyphRun.GlyphIndices.Skip(glyphCount),
GlyphRun.GlyphAdvances.Skip(glyphCount),
GlyphRun.GlyphOffsets.Skip(glyphCount),
GlyphRun.Characters.Skip(length),
GlyphRun.GlyphClusters.Skip(length));
var secondTextRun = new ShapedTextRun(secondGlyphRun, Style);
return new SplitTextCharactersResult(firstTextRun, secondTextRun);
}
public readonly struct SplitTextCharactersResult
{
public SplitTextCharactersResult(ShapedTextRun first, ShapedTextRun second)
{
First = first;
Second = second;
}
/// <summary>
/// Gets the first text run.
/// </summary>
/// <value>
/// The first text run.
/// </value>
public ShapedTextRun First { get; }
/// <summary>
/// Gets the second text run.
/// </summary>
/// <value>
/// The second text run.
/// </value>
public ShapedTextRun Second { get; }
}
}
}

395
src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs

@ -1,395 +0,0 @@
using System;
using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utility;
namespace Avalonia.Media.TextFormatting
{
internal class SimpleTextFormatter : TextFormatter
{
private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
/// <inheritdoc cref="TextFormatter.FormatLine"/>
public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
TextParagraphProperties paragraphProperties)
{
var textTrimming = paragraphProperties.TextTrimming;
var textWrapping = paragraphProperties.TextWrapping;
TextLine textLine;
var textRuns = FormatTextRuns(textSource, firstTextSourceIndex, out var textPointer);
if (textTrimming != TextTrimming.None)
{
textLine = PerformTextTrimming(textPointer, textRuns, paragraphWidth, paragraphProperties);
}
else
{
if (textWrapping == TextWrapping.Wrap)
{
textLine = PerformTextWrapping(textPointer, textRuns, paragraphWidth, paragraphProperties);
}
else
{
var textLineMetrics =
TextLineMetrics.Create(textRuns, paragraphWidth, paragraphProperties.TextAlignment);
textLine = new SimpleTextLine(textPointer, textRuns, textLineMetrics);
}
}
return textLine;
}
/// <summary>
/// Formats text runs with optional text style overrides.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="firstTextSourceIndex">The first text source index.</param>
/// <param name="textPointer">The text pointer that covers the formatted text runs.</param>
/// <returns>
/// The formatted text runs.
/// </returns>
private List<ShapedTextRun> FormatTextRuns(ITextSource textSource, int firstTextSourceIndex, out TextPointer textPointer)
{
var start = -1;
var length = 0;
var textRuns = new List<ShapedTextRun>();
while (true)
{
var textRun = textSource.GetTextRun(firstTextSourceIndex + length);
if (start == -1)
{
start = textRun.Text.Start;
}
if (textRun is TextEndOfLine)
{
break;
}
switch (textRun)
{
case TextCharacters textCharacters:
var runText = textCharacters.Text;
while (!runText.IsEmpty)
{
var shapableTextStyleRun = CreateShapableTextStyleRun(runText, textRun.Style);
var shapedRun = new ShapedTextRun(runText.Take(shapableTextStyleRun.TextPointer.Length),
shapableTextStyleRun.Style);
textRuns.Add(shapedRun);
runText = runText.Skip(shapedRun.Text.Length);
}
break;
default:
throw new NotSupportedException("Run type not supported by the formatter.");
}
length += textRun.Text.Length;
}
textPointer = new TextPointer(start, length);
return textRuns;
}
/// <summary>
/// Performs text trimming and returns a trimmed line.
/// </summary>
/// <param name="paragraphWidth">A <see cref="double"/> value that specifies the width of the paragraph that the line fills.</param>
/// <param name="paragraphProperties">A <see cref="TextParagraphProperties"/> value that represents paragraph properties,
/// such as TextWrapping, TextAlignment, or TextStyle.</param>
/// <param name="textRuns">The text runs to perform the trimming on.</param>
/// <param name="text">The text that was used to construct the text runs.</param>
/// <returns></returns>
private static TextLine PerformTextTrimming(TextPointer text, IReadOnlyList<ShapedTextRun> textRuns,
double paragraphWidth, TextParagraphProperties paragraphProperties)
{
var textTrimming = paragraphProperties.TextTrimming;
var availableWidth = paragraphWidth;
var currentWidth = 0.0;
var runIndex = 0;
while (runIndex < textRuns.Count)
{
var currentRun = textRuns[runIndex];
currentWidth += currentRun.GlyphRun.Bounds.Width;
if (currentWidth > availableWidth)
{
var ellipsisRun = CreateEllipsisRun(currentRun.Style);
var measuredLength = MeasureText(currentRun, availableWidth - ellipsisRun.GlyphRun.Bounds.Width);
if (textTrimming == TextTrimming.WordEllipsis)
{
if (measuredLength < text.End)
{
var currentBreakPosition = 0;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition > measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
measuredLength = currentBreakPosition;
}
}
var splitResult = SplitTextRuns(textRuns, measuredLength);
var trimmedRuns = new List<ShapedTextRun>(splitResult.First.Count + 1);
trimmedRuns.AddRange(splitResult.First);
trimmedRuns.Add(ellipsisRun);
var textLineMetrics =
TextLineMetrics.Create(trimmedRuns, paragraphWidth, paragraphProperties.TextAlignment);
return new SimpleTextLine(text.Take(measuredLength), trimmedRuns, textLineMetrics);
}
availableWidth -= currentRun.GlyphRun.Bounds.Width;
runIndex++;
}
return new SimpleTextLine(text, textRuns,
TextLineMetrics.Create(textRuns, paragraphWidth, paragraphProperties.TextAlignment));
}
/// <summary>
/// Performs text wrapping returns a list of text lines.
/// </summary>
/// <param name="paragraphProperties">The text paragraph properties.</param>
/// <param name="textRuns">The text run'S.</param>
/// <param name="text">The text to analyze for break opportunities.</param>
/// <param name="paragraphWidth"></param>
/// <returns></returns>
private static TextLine PerformTextWrapping(TextPointer text, IReadOnlyList<ShapedTextRun> textRuns,
double paragraphWidth, TextParagraphProperties paragraphProperties)
{
var availableWidth = paragraphWidth;
var currentWidth = 0.0;
var runIndex = 0;
var length = 0;
while (runIndex < textRuns.Count)
{
var currentRun = textRuns[runIndex];
if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth)
{
var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth);
if (measuredLength < currentRun.Text.Length)
{
var currentBreakPosition = -1;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition > measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
if (currentBreakPosition != -1)
{
measuredLength = currentBreakPosition;
}
}
length += measuredLength;
var splitResult = SplitTextRuns(textRuns, length);
var textLineMetrics =
TextLineMetrics.Create(splitResult.First, paragraphWidth, paragraphProperties.TextAlignment);
return new SimpleTextLine(text.Take(length), splitResult.First, textLineMetrics);
}
currentWidth += currentRun.GlyphRun.Bounds.Width;
length += currentRun.GlyphRun.Characters.Length;
runIndex++;
}
return new SimpleTextLine(text, textRuns,
TextLineMetrics.Create(textRuns, paragraphWidth, paragraphProperties.TextAlignment));
}
/// <summary>
/// Measures the number of characters that fits into available width.
/// </summary>
/// <param name="textRun">The text run.</param>
/// <param name="availableWidth">The available width.</param>
/// <returns></returns>
private static int MeasureText(ShapedTextRun textRun, double availableWidth)
{
var glyphRun = textRun.GlyphRun;
var characterHit = glyphRun.GetCharacterHitFromDistance(availableWidth, out _);
return characterHit.FirstCharacterIndex + characterHit.TrailingLength - textRun.Text.Start;
}
/// <summary>
/// Creates an ellipsis.
/// </summary>
/// <param name="textStyle">The text style.</param>
/// <returns></returns>
private static ShapedTextRun CreateEllipsisRun(TextStyle textStyle)
{
var formatterImpl = AvaloniaLocator.Current.GetService<ITextShaperImpl>();
var glyphRun = formatterImpl.ShapeText(s_ellipsis, textStyle.TextFormat);
return new ShapedTextRun(glyphRun, textStyle);
}
private readonly struct SplitTextRunsResult
{
public SplitTextRunsResult(IReadOnlyList<ShapedTextRun> first, IReadOnlyList<ShapedTextRun> second)
{
First = first;
Second = second;
}
/// <summary>
/// Gets the first text runs.
/// </summary>
/// <value>
/// The first text runs.
/// </value>
public IReadOnlyList<ShapedTextRun> First { get; }
/// <summary>
/// Gets the second text runs.
/// </summary>
/// <value>
/// The second text runs.
/// </value>
public IReadOnlyList<ShapedTextRun> Second { get; }
}
/// <summary>
/// Split a sequence of runs into two segments at specified length.
/// </summary>
/// <param name="textRuns">The text run's.</param>
/// <param name="length">The length to split at.</param>
/// <returns></returns>
private static SplitTextRunsResult SplitTextRuns(IReadOnlyList<ShapedTextRun> textRuns, int length)
{
var currentLength = 0;
for (var i = 0; i < textRuns.Count; i++)
{
var currentRun = textRuns[i];
if (currentLength + currentRun.GlyphRun.Characters.Length < length)
{
currentLength += currentRun.GlyphRun.Characters.Length;
continue;
}
var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
var first = new ShapedTextRun[firstCount];
if (firstCount > 1)
{
for (var j = 0; j < i; j++)
{
first[j] = textRuns[j];
}
}
var secondCount = textRuns.Count - firstCount;
if (currentLength + currentRun.GlyphRun.Characters.Length == length)
{
var second = new ShapedTextRun[secondCount];
var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
if (secondCount > 0)
{
for (var j = 0; j < secondCount; j++)
{
second[j] = textRuns[i + j + offset];
}
}
first[i] = currentRun;
return new SplitTextRunsResult(first, second);
}
else
{
secondCount++;
var second = new ShapedTextRun[secondCount];
if (secondCount > 0)
{
for (var j = 1; j < secondCount; j++)
{
second[j] = textRuns[i + j];
}
}
var split = currentRun.Split(length - currentLength);
first[i] = split.First;
second[0] = split.Second;
return new SplitTextRunsResult(first, second);
}
}
return new SplitTextRunsResult(textRuns, null);
}
}
}

259
src/Avalonia.Visuals/Media/TextFormatting/SimpleTextLine.cs

@ -1,259 +0,0 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
namespace Avalonia.Media.TextFormatting
{
internal class SimpleTextLine : TextLine
{
private readonly IReadOnlyList<ShapedTextRun> _textRuns;
public SimpleTextLine(TextPointer textPointer, IReadOnlyList<ShapedTextRun> textRuns, TextLineMetrics lineMetrics)
{
Text = textPointer;
_textRuns = textRuns;
LineMetrics = lineMetrics;
}
/// <inheritdoc/>
public override TextPointer Text { get; }
/// <inheritdoc/>
public override IReadOnlyList<TextRun> TextRuns => _textRuns;
/// <inheritdoc/>
public override TextLineMetrics LineMetrics { get; }
/// <inheritdoc/>
public override void Draw(IDrawingContextImpl drawingContext, Point origin)
{
var currentX = origin.X;
foreach (var textRun in _textRuns)
{
var baselineOrigin = new Point(currentX + LineMetrics.BaselineOrigin.X,
origin.Y + LineMetrics.BaselineOrigin.Y);
textRun.Draw(drawingContext, baselineOrigin);
currentX += textRun.Bounds.Width;
}
}
/// <inheritdoc/>
public override CharacterHit GetCharacterHitFromDistance(double distance)
{
if (distance < 0)
{
// hit happens before the line, return the first position
return new CharacterHit(Text.Start);
}
// process hit that happens within the line
var characterHit = new CharacterHit();
foreach (var run in _textRuns)
{
characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _);
if (distance <= run.Bounds.Width)
{
break;
}
distance -= run.Bounds.Width;
}
return characterHit;
}
/// <inheritdoc/>
public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{
return DistanceFromCodepointIndex(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0));
}
/// <inheritdoc/>
public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
{
int nextVisibleCp;
bool navigableCpFound;
if (characterHit.TrailingLength == 0)
{
navigableCpFound = FindNextCodepointIndex(characterHit.FirstCharacterIndex, out nextVisibleCp);
if (navigableCpFound)
{
// Move from leading to trailing edge
return new CharacterHit(nextVisibleCp, 1);
}
}
navigableCpFound = FindNextCodepointIndex(characterHit.FirstCharacterIndex + 1, out nextVisibleCp);
if (navigableCpFound)
{
// Move from trailing edge of current character to trailing edge of next
return new CharacterHit(nextVisibleCp, 1);
}
// Can't move, we're after the last character
return characterHit;
}
/// <inheritdoc/>
public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
{
int previousCodepointIndex;
bool codepointIndexFound;
var cpHit = characterHit.FirstCharacterIndex;
var trailingHit = characterHit.TrailingLength != 0;
// Input can be right after the end of the current line. Snap it to be at the end of the line.
if (cpHit >= Text.Start + Text.Length)
{
cpHit = Text.Start + Text.Length - 1;
trailingHit = true;
}
if (trailingHit)
{
codepointIndexFound = FindPreviousCodepointIndex(cpHit, out previousCodepointIndex);
if (codepointIndexFound)
{
// Move from trailing to leading edge
return new CharacterHit(previousCodepointIndex, 0);
}
}
codepointIndexFound = FindPreviousCodepointIndex(cpHit - 1, out previousCodepointIndex);
if (codepointIndexFound)
{
// Move from leading edge of current character to leading edge of previous
return new CharacterHit(previousCodepointIndex, 0);
}
// Can't move, we're before the first character
return characterHit;
}
/// <inheritdoc/>
public override CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit)
{
// same operation as move-to-previous
return GetPreviousCaretCharacterHit(characterHit);
}
/// <summary>
/// Get distance from line start to the specified codepoint index
/// </summary>
private double DistanceFromCodepointIndex(int codepointIndex)
{
var currentDistance = 0.0;
foreach (var textRun in _textRuns)
{
if (codepointIndex > textRun.Text.End)
{
currentDistance += textRun.Bounds.Width;
continue;
}
return currentDistance + textRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(codepointIndex));
}
return currentDistance;
}
/// <summary>
/// Search forward from the given codepoint index (inclusive) to find the next navigable codepoint index.
/// Return true if one such codepoint index is found, false otherwise.
/// </summary>
private bool FindNextCodepointIndex(int codepointIndex, out int nextCodepointIndex)
{
nextCodepointIndex = codepointIndex;
if (codepointIndex >= Text.Start + Text.Length)
{
return false; // Cannot go forward anymore
}
GetRunIndexAtCodepointIndex(codepointIndex, out var runIndex, out var cpRunStart);
while (runIndex < TextRuns.Count)
{
// When navigating forward, only the trailing edge of visible content is
// navigable.
if (runIndex < TextRuns.Count)
{
nextCodepointIndex = Math.Max(cpRunStart, codepointIndex);
return true;
}
cpRunStart += TextRuns[runIndex++].Text.Length;
}
return false;
}
/// <summary>
/// Search backward from the given codepoint index (inclusive) to find the previous navigable codepoint index.
/// Return true if one such codepoint is found, false otherwise.
/// </summary>
private bool FindPreviousCodepointIndex(int codepointIndex, out int previousCodepointIndex)
{
previousCodepointIndex = codepointIndex;
if (codepointIndex < Text.Start)
{
return false; // Cannot go backward anymore.
}
// Position the cpRunEnd at the end of the span that contains the given cp
GetRunIndexAtCodepointIndex(codepointIndex, out var runIndex, out var codepointIndexAtRunEnd);
codepointIndexAtRunEnd += TextRuns[runIndex].Text.End;
while (runIndex >= 0)
{
// Visible content has caret stops at its leading edge.
if (runIndex + 1 < TextRuns.Count)
{
previousCodepointIndex = Math.Min(codepointIndexAtRunEnd, codepointIndex);
return true;
}
// Newline sequence has caret stops at its leading edge.
if (runIndex == TextRuns.Count)
{
// Get the cp index at the beginning of the newline sequence.
previousCodepointIndex = codepointIndexAtRunEnd - TextRuns[runIndex].Text.Length + 1;
return true;
}
codepointIndexAtRunEnd -= TextRuns[runIndex--].Text.Length;
}
return false;
}
private void GetRunIndexAtCodepointIndex(int codepointIndex, out int runIndex, out int codepointIndexAtRunStart)
{
codepointIndexAtRunStart = Text.Start;
runIndex = 0;
// Find the span that contains the given cp
while (runIndex < TextRuns.Count &&
codepointIndexAtRunStart + TextRuns[runIndex].Text.Length <= codepointIndex)
{
codepointIndexAtRunStart += TextRuns[runIndex++].Text.Length;
}
}
}
}

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

@ -1,4 +1,6 @@
using Avalonia.Utility;
using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
@ -7,15 +9,182 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public class TextCharacters : TextRun
{
protected TextCharacters()
public TextCharacters(ReadOnlySlice<char> text, TextRunProperties properties)
{
TextSourceLength = text.Length;
Text = text;
Properties = properties;
}
public TextCharacters(ReadOnlySlice<char> text, TextStyle style)
/// <inheritdoc />
public override int TextSourceLength { get; }
/// <inheritdoc />
public override ReadOnlySlice<char> Text { get; }
/// <inheritdoc />
public override TextRunProperties Properties { get; }
/// <summary>
/// Gets a list of <see cref="ShapeableTextCharacters"/>.
/// </summary>
/// <returns>The shapeable text characters.</returns>
internal IList<ShapeableTextCharacters> GetShapeableCharacters()
{
Text = text;
Style = style;
var shapeableCharacters = new List<ShapeableTextCharacters>(2);
var runText = Text;
while (!runText.IsEmpty)
{
var shapeableRun = CreateShapeableRun(runText, Properties);
shapeableCharacters.Add(shapeableRun);
runText = runText.Skip(shapeableRun.Text.Length);
}
return shapeableCharacters;
}
/// <summary>
/// Creates a shapeable text run with unique properties.
/// </summary>
/// <param name="text">The text to create text runs from.</param>
/// <param name="defaultProperties">The default text run properties.</param>
/// <returns>A list of shapeable text runs.</returns>
private ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice<char> text, TextRunProperties defaultProperties)
{
var defaultTypeface = defaultProperties.Typeface;
var currentTypeface = defaultTypeface;
if (TryGetRunProperties(text, currentTypeface, defaultTypeface, out var count))
{
return new ShapeableTextCharacters(text.Take(count),
new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize,
defaultProperties.TextDecorations, defaultProperties.ForegroundBrush));
}
var codepoint = Codepoint.ReadAt(text, count, out _);
//ToDo: Fix FontFamily fallback
currentTypeface =
FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Weight, defaultTypeface.Style, defaultTypeface.FontFamily);
if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
{
//Fallback found
return new ShapeableTextCharacters(text.Take(count),
new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize,
defaultProperties.TextDecorations, defaultProperties.ForegroundBrush));
}
// no fallback found
currentTypeface = defaultTypeface;
var glyphTypeface = currentTypeface.GlyphTypeface;
var enumerator = new GraphemeEnumerator(text);
while (enumerator.MoveNext())
{
var grapheme = enumerator.Current;
if (!grapheme.FirstCodepoint.IsWhiteSpace && glyphTypeface.TryGetGlyph(grapheme.FirstCodepoint, out _))
{
break;
}
count += grapheme.Text.Length;
}
return new ShapeableTextCharacters(text.Take(count),
new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize,
defaultProperties.TextDecorations, defaultProperties.ForegroundBrush));
}
/// <summary>
/// Tries to get run properties.
/// </summary>
/// <param name="defaultTypeface"></param>
/// <param name="text"></param>
/// <param name="typeface">The typeface that is used to find matching characters.</param>
/// <param name="count"></param>
/// <returns></returns>
protected bool TryGetRunProperties(ReadOnlySlice<char> text, Typeface typeface, Typeface defaultTypeface,
out int count)
{
if (text.Length == 0)
{
count = 0;
return false;
}
var isFallback = typeface != defaultTypeface;
count = 0;
var script = Script.Common;
//var direction = BiDiClass.LeftToRight;
var font = typeface.GlyphTypeface;
var defaultFont = defaultTypeface.GlyphTypeface;
var enumerator = new GraphemeEnumerator(text);
while (enumerator.MoveNext())
{
var grapheme = enumerator.Current;
var currentScript = grapheme.FirstCodepoint.Script;
//var currentDirection = grapheme.FirstCodepoint.BiDiClass;
//// ToDo: Implement BiDi algorithm
//if (currentScript.HorizontalDirection != direction)
//{
// if (!UnicodeUtility.IsWhiteSpace(grapheme.FirstCodepoint))
// {
// break;
// }
//}
if (currentScript != script)
{
if (currentScript != Script.Inherited && currentScript != Script.Common)
{
if (script == Script.Inherited || script == Script.Common)
{
script = currentScript;
}
else
{
break;
}
}
}
if (isFallback)
{
if (defaultFont.TryGetGlyph(grapheme.FirstCodepoint, out _))
{
break;
}
}
if (!font.TryGetGlyph(grapheme.FirstCodepoint, out _))
{
if (!grapheme.FirstCodepoint.IsWhiteSpace)
{
break;
}
}
count += grapheme.Text.Length;
}
return count > 0;
}
}
}

71
src/Avalonia.Visuals/Media/TextFormatting/TextFormat.cs

@ -1,71 +0,0 @@
using System;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Unique text formatting properties that are used by the <see cref="TextFormatter"/>.
/// </summary>
public readonly struct TextFormat : IEquatable<TextFormat>
{
public TextFormat(Typeface typeface, double fontRenderingEmSize)
{
Typeface = typeface;
FontRenderingEmSize = fontRenderingEmSize;
FontMetrics = new FontMetrics(typeface, fontRenderingEmSize);
}
/// <summary>
/// Gets the typeface.
/// </summary>
/// <value>
/// The typeface.
/// </value>
public Typeface Typeface { get; }
/// <summary>
/// Gets the font rendering em size.
/// </summary>
/// <value>
/// The em rendering size of the font.
/// </value>
public double FontRenderingEmSize { get; }
/// <summary>
/// Gets the font metrics.
/// </summary>
/// <value>
/// The metrics of the font.
/// </value>
public FontMetrics FontMetrics { get; }
public static bool operator ==(TextFormat self, TextFormat other)
{
return self.Equals(other);
}
public static bool operator !=(TextFormat self, TextFormat other)
{
return !(self == other);
}
public bool Equals(TextFormat other)
{
return Typeface.Equals(other.Typeface) && FontRenderingEmSize.Equals(other.FontRenderingEmSize);
}
public override bool Equals(object obj)
{
return obj is TextFormat other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (Typeface != null ? Typeface.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ FontRenderingEmSize.GetHashCode();
return hashCode;
}
}
}
}

148
src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs

@ -1,5 +1,4 @@
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utility;
namespace Avalonia.Media.TextFormatting
{
@ -22,7 +21,7 @@ namespace Avalonia.Media.TextFormatting
return current;
}
current = new SimpleTextFormatter();
current = new TextFormatterImpl();
AvaloniaLocator.CurrentMutable.Bind<TextFormatter>().ToConstant(current);
@ -38,149 +37,10 @@ namespace Avalonia.Media.TextFormatting
/// <param name="paragraphWidth">A <see cref="double"/> value that specifies the width of the paragraph that the line fills.</param>
/// <param name="paragraphProperties">A <see cref="TextParagraphProperties"/> value that represents paragraph properties,
/// such as TextWrapping, TextAlignment, or TextStyle.</param>
/// <param name="previousLineBreak">A <see cref="TextLineBreak"/> value that specifies the text formatter state,
/// in terms of where the previous line in the paragraph was broken by the text formatting process.</param>
/// <returns>The formatted line.</returns>
public abstract TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
TextParagraphProperties paragraphProperties);
/// <summary>
/// Creates a text style run with unique properties.
/// </summary>
/// <param name="text">The text to create text runs from.</param>
/// <param name="defaultStyle"></param>
/// <returns>A list of text runs.</returns>
protected TextStyleRun CreateShapableTextStyleRun(ReadOnlySlice<char> text, TextStyle defaultStyle)
{
var defaultTypeface = defaultStyle.TextFormat.Typeface;
var currentTypeface = defaultTypeface;
if (TryGetRunProperties(text, currentTypeface, defaultTypeface, out var count))
{
return new TextStyleRun(new TextPointer(text.Start, count), new TextStyle(currentTypeface,
defaultStyle.TextFormat.FontRenderingEmSize,
defaultStyle.Foreground, defaultStyle.TextDecorations));
}
var codepoint = Codepoint.ReadAt(text, count, out _);
//ToDo: Fix FontFamily fallback
currentTypeface =
FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Weight, defaultTypeface.Style, defaultStyle.TextFormat.Typeface.FontFamily);
if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
{
//Fallback found
return new TextStyleRun(new TextPointer(text.Start, count), new TextStyle(currentTypeface,
defaultStyle.TextFormat.FontRenderingEmSize,
defaultStyle.Foreground, defaultStyle.TextDecorations));
}
// no fallback found
currentTypeface = defaultTypeface;
var glyphTypeface = currentTypeface.GlyphTypeface;
var enumerator = new GraphemeEnumerator(text);
while (enumerator.MoveNext())
{
var grapheme = enumerator.Current;
if (!grapheme.FirstCodepoint.IsWhiteSpace && glyphTypeface.TryGetGlyph(grapheme.FirstCodepoint, out _))
{
break;
}
count += grapheme.Text.Length;
}
return new TextStyleRun(new TextPointer(text.Start, count),
new TextStyle(currentTypeface, defaultStyle.TextFormat.FontRenderingEmSize,
defaultStyle.Foreground, defaultStyle.TextDecorations));
}
/// <summary>
/// Tries to get run properties.
/// </summary>
/// <param name="defaultTypeface"></param>
/// <param name="text"></param>
/// <param name="typeface">The typeface that is used to find matching characters.</param>
/// <param name="count"></param>
/// <returns></returns>
protected bool TryGetRunProperties(ReadOnlySlice<char> text, Typeface typeface, Typeface defaultTypeface,
out int count)
{
if (text.Length == 0)
{
count = 0;
return false;
}
var isFallback = typeface != defaultTypeface;
count = 0;
var script = Script.Common;
//var direction = BiDiClass.LeftToRight;
var font = typeface.GlyphTypeface;
var defaultFont = defaultTypeface.GlyphTypeface;
var enumerator = new GraphemeEnumerator(text);
while (enumerator.MoveNext())
{
var grapheme = enumerator.Current;
var currentScript = grapheme.FirstCodepoint.Script;
//var currentDirection = grapheme.FirstCodepoint.BiDiClass;
//// ToDo: Implement BiDi algorithm
//if (currentScript.HorizontalDirection != direction)
//{
// if (!UnicodeUtility.IsWhiteSpace(grapheme.FirstCodepoint))
// {
// break;
// }
//}
if (currentScript != script)
{
if (currentScript != Script.Inherited && currentScript != Script.Common)
{
if (script == Script.Inherited || script == Script.Common)
{
script = currentScript;
}
else
{
break;
}
}
}
if (isFallback)
{
if (defaultFont.TryGetGlyph(grapheme.FirstCodepoint, out _))
{
break;
}
}
if (!font.TryGetGlyph(grapheme.FirstCodepoint, out _))
{
if (!grapheme.FirstCodepoint.IsWhiteSpace)
{
break;
}
}
count += grapheme.Text.Length;
}
return count > 0;
}
TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null);
}
}

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

@ -0,0 +1,544 @@
using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
internal class TextFormatterImpl : TextFormatter
{
private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
/// <inheritdoc cref="TextFormatter.FormatLine"/>
public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null)
{
var textTrimming = paragraphProperties.TextTrimming;
var textWrapping = paragraphProperties.TextWrapping;
TextLine textLine = null;
var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak, out var nextLineBreak);
var textRange = GetTextRange(textRuns);
if (textTrimming != TextTrimming.None)
{
textLine = PerformTextTrimming(textRuns, textRange, paragraphWidth, paragraphProperties);
}
else
{
switch (textWrapping)
{
case TextWrapping.NoWrap:
{
var textLineMetrics =
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties);
textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak);
break;
}
case TextWrapping.WrapWithOverflow:
case TextWrapping.Wrap:
{
textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties);
break;
}
}
}
return textLine;
}
/// <summary>
/// Fetches text runs.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="firstTextSourceIndex">The first text source index.</param>
/// <param name="previousLineBreak">Previous line break. Can be null.</param>
/// <param name="nextLineBreak">Next line break. Can be null.</param>
/// <returns>
/// The formatted text runs.
/// </returns>
private static IReadOnlyList<ShapedTextCharacters> FetchTextRuns(ITextSource textSource,
int firstTextSourceIndex, TextLineBreak previousLineBreak, out TextLineBreak nextLineBreak)
{
nextLineBreak = default;
var currentLength = 0;
var textRuns = new List<ShapedTextCharacters>();
if (previousLineBreak != null)
{
foreach (var shapedCharacters in previousLineBreak.RemainingCharacters)
{
textRuns.Add(shapedCharacters);
if (TryGetLineBreak(shapedCharacters, out var runLineBreak))
{
var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap);
nextLineBreak = new TextLineBreak(splitResult.Second);
return splitResult.First;
}
currentLength += shapedCharacters.Text.Length;
}
}
firstTextSourceIndex += currentLength;
var textRunEnumerator = new TextRunEnumerator(textSource, firstTextSourceIndex);
while (textRunEnumerator.MoveNext())
{
var textRun = textRunEnumerator.Current;
switch (textRun)
{
case TextCharacters textCharacters:
{
var shapeableRuns = textCharacters.GetShapeableCharacters();
foreach (var run in shapeableRuns)
{
var glyphRun = TextShaper.Current.ShapeText(run.Text, run.Properties.Typeface,
run.Properties.FontRenderingEmSize, run.Properties.CultureInfo);
var shapedCharacters = new ShapedTextCharacters(glyphRun, textRun.Properties);
textRuns.Add(shapedCharacters);
}
break;
}
}
if (TryGetLineBreak(textRun, out var runLineBreak))
{
var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap);
nextLineBreak = new TextLineBreak(splitResult.Second);
return splitResult.First;
}
currentLength += textRun.Text.Length;
}
return textRuns;
}
private static bool TryGetLineBreak(TextRun textRun, out LineBreak lineBreak)
{
lineBreak = default;
if (textRun.Text.IsEmpty)
{
return false;
}
var lineBreakEnumerator = new LineBreakEnumerator(textRun.Text);
while (lineBreakEnumerator.MoveNext())
{
if (!lineBreakEnumerator.Current.Required)
{
continue;
}
lineBreak = lineBreakEnumerator.Current;
if (lineBreak.PositionWrap >= textRun.Text.Length)
{
return true;
}
//The line breaker isn't treating \n\r as a pair so we have to fix that here.
if (textRun.Text[lineBreak.PositionMeasure] == '\n'
&& textRun.Text[lineBreak.PositionWrap] == '\r')
{
lineBreak = new LineBreak(lineBreak.PositionMeasure, lineBreak.PositionWrap + 1,
lineBreak.Required);
}
return true;
}
return false;
}
/// <summary>
/// Performs text trimming and returns a trimmed line.
/// </summary>
/// <param name="textRuns">The text runs to perform the trimming on.</param>
/// <param name="textRange">The text range that is covered by the text runs.</param>
/// <param name="paragraphWidth">A <see cref="double"/> value that specifies the width of the paragraph that the line fills.</param>
/// <param name="paragraphProperties">A <see cref="TextParagraphProperties"/> value that represents paragraph properties,
/// such as TextWrapping, TextAlignment, or TextStyle.</param>
/// <returns></returns>
private static TextLine PerformTextTrimming(IReadOnlyList<ShapedTextCharacters> textRuns, TextRange textRange,
double paragraphWidth, TextParagraphProperties paragraphProperties)
{
var textTrimming = paragraphProperties.TextTrimming;
var availableWidth = paragraphWidth;
var currentWidth = 0.0;
var runIndex = 0;
while (runIndex < textRuns.Count)
{
var currentRun = textRuns[runIndex];
currentWidth += currentRun.GlyphRun.Bounds.Width;
if (currentWidth > availableWidth)
{
var ellipsisRun = CreateEllipsisRun(currentRun.Properties);
var measuredLength = MeasureText(currentRun, availableWidth - ellipsisRun.GlyphRun.Bounds.Width);
if (textTrimming == TextTrimming.WordEllipsis)
{
if (measuredLength < textRange.End)
{
var currentBreakPosition = 0;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition > measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
measuredLength = currentBreakPosition;
}
}
var splitResult = SplitTextRuns(textRuns, measuredLength);
var trimmedRuns = new List<ShapedTextCharacters>(splitResult.First.Count + 1);
trimmedRuns.AddRange(splitResult.First);
trimmedRuns.Add(ellipsisRun);
var textLineMetrics =
TextLineMetrics.Create(trimmedRuns, textRange, paragraphWidth, paragraphProperties);
return new TextLineImpl(trimmedRuns, textLineMetrics);
}
availableWidth -= currentRun.GlyphRun.Bounds.Width;
runIndex++;
}
return new TextLineImpl(textRuns,
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties));
}
/// <summary>
/// Performs text wrapping returns a list of text lines.
/// </summary>
/// <param name="textRuns">The text run's.</param>
/// <param name="textRange">The text range that is covered by the text runs.</param>
/// <param name="paragraphWidth">The paragraph width.</param>
/// <param name="paragraphProperties">The text paragraph properties.</param>
/// <returns>The wrapped text line.</returns>
private static TextLine PerformTextWrapping(IReadOnlyList<ShapedTextCharacters> textRuns, TextRange textRange,
double paragraphWidth, TextParagraphProperties paragraphProperties)
{
var availableWidth = paragraphWidth;
var currentWidth = 0.0;
var runIndex = 0;
var length = 0;
while (runIndex < textRuns.Count)
{
var currentRun = textRuns[runIndex];
if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth)
{
var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth);
if (measuredLength < currentRun.Text.Length)
{
if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
{
var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(measuredLength));
if (lineBreaker.MoveNext())
{
measuredLength += lineBreaker.Current.PositionWrap;
}
else
{
measuredLength = currentRun.Text.Length;
}
}
else
{
var currentBreakPosition = -1;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition > measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
if (currentBreakPosition != -1)
{
measuredLength = currentBreakPosition;
}
}
}
length += measuredLength;
var splitResult = SplitTextRuns(textRuns, length);
var textLineMetrics = TextLineMetrics.Create(splitResult.First,
new TextRange(textRange.Start, length), paragraphWidth, paragraphProperties);
var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ?
new TextLineBreak(splitResult.Second) :
null;
return new TextLineImpl(splitResult.First, textLineMetrics, lineBreak);
}
currentWidth += currentRun.GlyphRun.Bounds.Width;
length += currentRun.GlyphRun.Characters.Length;
runIndex++;
}
return new TextLineImpl(textRuns,
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties));
}
/// <summary>
/// Measures the number of characters that fits into available width.
/// </summary>
/// <param name="textCharacters">The text run.</param>
/// <param name="availableWidth">The available width.</param>
/// <returns></returns>
private static int MeasureText(ShapedTextCharacters textCharacters, double availableWidth)
{
var glyphRun = textCharacters.GlyphRun;
var characterHit = glyphRun.GetCharacterHitFromDistance(availableWidth, out _);
return characterHit.FirstCharacterIndex + characterHit.TrailingLength - textCharacters.Text.Start;
}
/// <summary>
/// Creates an ellipsis.
/// </summary>
/// <param name="properties">The text run properties.</param>
/// <returns></returns>
private static ShapedTextCharacters CreateEllipsisRun(TextRunProperties properties)
{
var formatterImpl = AvaloniaLocator.Current.GetService<ITextShaperImpl>();
var glyphRun = formatterImpl.ShapeText(s_ellipsis, properties.Typeface, properties.FontRenderingEmSize,
properties.CultureInfo);
return new ShapedTextCharacters(glyphRun, properties);
}
/// <summary>
/// Gets the text range that is covered by the text runs.
/// </summary>
/// <param name="textRuns">The text runs.</param>
/// <returns>The text range that is covered by the text runs.</returns>
private static TextRange GetTextRange(IReadOnlyList<TextRun> textRuns)
{
if (textRuns is null || textRuns.Count == 0)
{
return new TextRange();
}
var firstTextRun = textRuns[0];
if (textRuns.Count == 1)
{
return new TextRange(firstTextRun.Text.Start, firstTextRun.Text.Length);
}
var start = firstTextRun.Text.Start;
var end = textRuns[textRuns.Count - 1].Text.End + 1;
return new TextRange(start, end - start);
}
/// <summary>
/// Split a sequence of runs into two segments at specified length.
/// </summary>
/// <param name="textRuns">The text run's.</param>
/// <param name="length">The length to split at.</param>
/// <returns>The split text runs.</returns>
private static SplitTextRunsResult SplitTextRuns(IReadOnlyList<ShapedTextCharacters> textRuns, int length)
{
var currentLength = 0;
for (var i = 0; i < textRuns.Count; i++)
{
var currentRun = textRuns[i];
if (currentLength + currentRun.GlyphRun.Characters.Length < length)
{
currentLength += currentRun.GlyphRun.Characters.Length;
continue;
}
var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
var first = new ShapedTextCharacters[firstCount];
if (firstCount > 1)
{
for (var j = 0; j < i; j++)
{
first[j] = textRuns[j];
}
}
var secondCount = textRuns.Count - firstCount;
if (currentLength + currentRun.GlyphRun.Characters.Length == length)
{
var second = new ShapedTextCharacters[secondCount];
var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
if (secondCount > 0)
{
for (var j = 0; j < secondCount; j++)
{
second[j] = textRuns[i + j + offset];
}
}
first[i] = currentRun;
return new SplitTextRunsResult(first, second);
}
else
{
secondCount++;
var second = new ShapedTextCharacters[secondCount];
if (secondCount > 0)
{
for (var j = 1; j < secondCount; j++)
{
second[j] = textRuns[i + j];
}
}
var split = currentRun.Split(length - currentLength);
first[i] = split.First;
second[0] = split.Second;
return new SplitTextRunsResult(first, second);
}
}
return new SplitTextRunsResult(textRuns, null);
}
private readonly struct SplitTextRunsResult
{
public SplitTextRunsResult(IReadOnlyList<ShapedTextCharacters> first, IReadOnlyList<ShapedTextCharacters> second)
{
First = first;
Second = second;
}
/// <summary>
/// Gets the first text runs.
/// </summary>
/// <value>
/// The first text runs.
/// </value>
public IReadOnlyList<ShapedTextCharacters> First { get; }
/// <summary>
/// Gets the second text runs.
/// </summary>
/// <value>
/// The second text runs.
/// </value>
public IReadOnlyList<ShapedTextCharacters> Second { get; }
}
private struct TextRunEnumerator
{
private readonly ITextSource _textSource;
private int _pos;
public TextRunEnumerator(ITextSource textSource, int firstTextSourceIndex)
{
_textSource = textSource;
_pos = firstTextSourceIndex;
Current = null;
}
// ReSharper disable once MemberHidesStaticFromOuterClass
public TextRun Current { get; private set; }
public bool MoveNext()
{
Current = _textSource.GetTextRun(_pos);
if (Current is null)
{
return false;
}
if (Current.TextSourceLength == 0)
{
return false;
}
_pos += Current.TextSourceLength;
return !(Current is TextEndOfLine);
}
}
}
}

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

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Media.Immutable;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.Utility;
using Avalonia.Platform;
namespace Avalonia.Media.TextFormatting
{
@ -14,11 +12,11 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public class TextLayout
{
private static readonly ReadOnlySlice<char> s_empty = new ReadOnlySlice<char>(new[] { '\u200B' });
private static readonly char[] s_empty = { '\u200B' };
private readonly ReadOnlySlice<char> _text;
private readonly TextParagraphProperties _paragraphProperties;
private readonly IReadOnlyList<TextStyleRun> _textStyleOverrides;
private readonly IReadOnlyList<ValueSpan<TextRunProperties>> _textStyleOverrides;
/// <summary>
/// Initializes a new instance of the <see cref="TextLayout" /> class.
@ -33,6 +31,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="textDecorations">The text decorations.</param>
/// <param name="maxWidth">The maximum width.</param>
/// <param name="maxHeight">The maximum height.</param>
/// <param name="lineHeight">The height of each line of text.</param>
/// <param name="maxLines">The maximum number of text lines.</param>
/// <param name="textStyleOverrides">The text style overrides.</param>
public TextLayout(
@ -46,18 +45,22 @@ namespace Avalonia.Media.TextFormatting
TextDecorationCollection textDecorations = null,
double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity,
double lineHeight = double.NaN,
int maxLines = 0,
IReadOnlyList<TextStyleRun> textStyleOverrides = null)
IReadOnlyList<ValueSpan<TextRunProperties>> textStyleOverrides = null)
{
_text = string.IsNullOrEmpty(text) ?
new ReadOnlySlice<char>() :
new ReadOnlySlice<char>(text.AsMemory());
_paragraphProperties =
CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, textTrimming, textDecorations?.ToImmutable());
CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, textTrimming,
textDecorations, lineHeight);
_textStyleOverrides = textStyleOverrides;
LineHeight = lineHeight;
MaxWidth = maxWidth;
MaxHeight = maxHeight;
@ -67,22 +70,29 @@ namespace Avalonia.Media.TextFormatting
UpdateLayout();
}
/// <summary>
/// Gets or sets the height of each line of text.
/// </summary>
/// <remarks>
/// A value of NaN (equivalent to an attribute value of "Auto") indicates that the line height
/// is determined automatically from the current font characteristics. The default is NaN.
/// </remarks>
public double LineHeight { get; }
/// <summary>
/// Gets the maximum width.
/// </summary>
public double MaxWidth { get; }
/// <summary>
/// Gets the maximum height.
/// </summary>
public double MaxHeight { get; }
/// <summary>
/// Gets the maximum number of text lines.
/// </summary>
public double MaxLines { get; }
public int MaxLines { get; }
/// <summary>
/// Gets the text lines.
@ -105,7 +115,7 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
/// <param name="context">The drawing context.</param>
/// <param name="origin">The origin.</param>
public void Draw(IDrawingContextImpl context, Point origin)
public void Draw(DrawingContext context, Point origin)
{
if (!TextLines.Any())
{
@ -132,14 +142,16 @@ namespace Avalonia.Media.TextFormatting
/// <param name="textWrapping">The text wrapping.</param>
/// <param name="textTrimming">The text trimming.</param>
/// <param name="textDecorations">The text decorations.</param>
/// <param name="lineHeight">The height of each line of text.</param>
/// <returns></returns>
private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize,
IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping, TextTrimming textTrimming,
ImmutableTextDecoration[] textDecorations)
TextDecorationCollection textDecorations, double lineHeight)
{
var textRunStyle = new TextStyle(typeface, fontSize, foreground, textDecorations);
var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground);
return new TextParagraphProperties(textRunStyle, textAlignment, textWrapping, textTrimming);
return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, textTrimming,
lineHeight);
}
/// <summary>
@ -170,14 +182,15 @@ namespace Avalonia.Media.TextFormatting
/// <returns>The empty text line.</returns>
private TextLine CreateEmptyTextLine(int startingIndex)
{
var textFormat = _paragraphProperties.DefaultTextStyle.TextFormat;
var properties = _paragraphProperties.DefaultTextRunProperties;
var glyphRun = TextShaper.Current.ShapeText(s_empty, textFormat);
var glyphRun = TextShaper.Current.ShapeText(new ReadOnlySlice<char>(s_empty, startingIndex, 1),
properties.Typeface, properties.FontRenderingEmSize, properties.CultureInfo);
var textRuns = new[] { new ShapedTextRun(glyphRun, _paragraphProperties.DefaultTextStyle) };
var textRuns = new[] { new ShapedTextCharacters(glyphRun, _paragraphProperties.DefaultTextRunProperties) };
return new SimpleTextLine(new TextPointer(startingIndex, 0), textRuns,
TextLineMetrics.Create(textRuns, MaxWidth, _paragraphProperties.TextAlignment));
return new TextLineImpl(textRuns,
TextLineMetrics.Create(textRuns, new TextRange(startingIndex, 1), MaxWidth, _paragraphProperties));
}
/// <summary>
@ -199,77 +212,38 @@ namespace Avalonia.Media.TextFormatting
double left = 0.0, right = 0.0, bottom = 0.0;
var lineBreaker = new LineBreakEnumerator(_text);
var currentPosition = 0;
var textSource = new FormattedTextSource(_text,
_paragraphProperties.DefaultTextRunProperties, _textStyleOverrides);
TextLineBreak previousLineBreak = null;
while (currentPosition < _text.Length && (MaxLines == 0 || textLines.Count < MaxLines))
{
int length;
var textLine = TextFormatter.Current.FormatLine(textSource, currentPosition, MaxWidth,
_paragraphProperties, previousLineBreak);
if (lineBreaker.MoveNext())
{
if (!lineBreaker.Current.Required)
{
continue;
}
previousLineBreak = textLine.LineBreak;
length = lineBreaker.Current.PositionWrap - currentPosition;
textLines.Add(textLine);
if (currentPosition + length < _text.Length)
{
//The line breaker isn't treating \n\r as a pair so we have to fix that here.
if (_text[lineBreaker.Current.PositionMeasure] == '\n'
&& _text[lineBreaker.Current.PositionWrap] == '\r')
{
length++;
}
}
}
else
UpdateBounds(textLine, ref left, ref right, ref bottom);
if (!double.IsPositiveInfinity(MaxHeight) && bottom > MaxHeight)
{
length = _text.Length - currentPosition;
break;
}
var remainingLength = length;
currentPosition += textLine.TextRange.Length;
while (remainingLength > 0 && (MaxLines == 0 || textLines.Count < MaxLines))
if (currentPosition != _text.Length || textLine.LineBreak == null)
{
var textSlice = _text.AsSlice(currentPosition, remainingLength);
var textSource = new FormattedTextSource(textSlice, _paragraphProperties.DefaultTextStyle, _textStyleOverrides);
var textLine = TextFormatter.Current.FormatLine(textSource, 0, MaxWidth, _paragraphProperties);
UpdateBounds(textLine, ref left, ref right, ref bottom);
textLines.Add(textLine);
if (!double.IsPositiveInfinity(MaxHeight) && bottom + textLine.LineMetrics.Size.Height > MaxHeight)
{
currentPosition = _text.Length;
break;
}
if (_paragraphProperties.TextTrimming != TextTrimming.None)
{
currentPosition += remainingLength;
break;
}
remainingLength -= textLine.Text.Length;
currentPosition += textLine.Text.Length;
continue;
}
}
if (lineBreaker.Current.Required && currentPosition == _text.Length)
{
var emptyTextLine = CreateEmptyTextLine(currentPosition);
UpdateBounds(emptyTextLine, ref left, ref right, ref bottom);
textLines.Add(emptyTextLine);
}
@ -279,22 +253,27 @@ namespace Avalonia.Media.TextFormatting
}
}
private struct FormattedTextSource : ITextSource
private readonly struct FormattedTextSource : ITextSource
{
private readonly ReadOnlySlice<char> _text;
private readonly TextStyle _defaultStyle;
private readonly IReadOnlyList<TextStyleRun> _textStyleOverrides;
private readonly TextRunProperties _defaultProperties;
private readonly IReadOnlyList<ValueSpan<TextRunProperties>> _textModifier;
public FormattedTextSource(ReadOnlySlice<char> text, TextStyle defaultStyle,
IReadOnlyList<TextStyleRun> textStyleOverrides)
public FormattedTextSource(ReadOnlySlice<char> text, TextRunProperties defaultProperties,
IReadOnlyList<ValueSpan<TextRunProperties>> textModifier)
{
_text = text;
_defaultStyle = defaultStyle;
_textStyleOverrides = textStyleOverrides;
_defaultProperties = defaultProperties;
_textModifier = textModifier;
}
public TextRun GetTextRun(int textSourceIndex)
{
if (textSourceIndex > _text.End)
{
return new TextEndOfLine();
}
var runText = _text.Skip(textSourceIndex);
if (runText.IsEmpty)
@ -302,30 +281,29 @@ namespace Avalonia.Media.TextFormatting
return new TextEndOfLine();
}
var textStyleRun = CreateTextStyleRunWithOverride(runText, _defaultStyle, _textStyleOverrides);
var textStyleRun = CreateTextStyleRun(runText, _defaultProperties, _textModifier);
return new TextCharacters(runText.Take(textStyleRun.TextPointer.Length), textStyleRun.Style);
return new TextCharacters(runText.Take(textStyleRun.Length), textStyleRun.Value);
}
/// <summary>
/// Creates a text style run that has overrides applied. Only overrides with equal TextStyle.
/// If optimizeForShaping is <c>true</c> Foreground is ignored.
/// Creates a span of text run properties that has modifier applied.
/// </summary>
/// <param name="text">The text to create the run for.</param>
/// <param name="defaultTextStyle">The default text style for segments that don't have an override.</param>
/// <param name="textStyleOverrides">The text style overrides.</param>
/// <param name="text">The text to create the properties for.</param>
/// <param name="defaultProperties">The default text properties.</param>
/// <param name="textModifier">The text properties modifier.</param>
/// <returns>
/// The created text style run.
/// </returns>
private static TextStyleRun CreateTextStyleRunWithOverride(ReadOnlySlice<char> text,
TextStyle defaultTextStyle, IReadOnlyList<TextStyleRun> textStyleOverrides)
private static ValueSpan<TextRunProperties> CreateTextStyleRun(ReadOnlySlice<char> text,
TextRunProperties defaultProperties, IReadOnlyList<ValueSpan<TextRunProperties>> textModifier)
{
if(textStyleOverrides == null || textStyleOverrides.Count == 0)
if (textModifier == null || textModifier.Count == 0)
{
return new TextStyleRun(new TextPointer(text.Start, text.Length), defaultTextStyle);
return new ValueSpan<TextRunProperties>(text.Start, text.Length, defaultProperties);
}
var currentTextStyle = defaultTextStyle;
var currentProperties = defaultProperties;
var hasOverride = false;
@ -333,35 +311,34 @@ namespace Avalonia.Media.TextFormatting
var length = 0;
for (; i < textStyleOverrides.Count; i++)
for (; i < textModifier.Count; i++)
{
var styleOverride = textStyleOverrides[i];
var propertiesOverride = textModifier[i];
var textPointer = styleOverride.TextPointer;
var textRange = new TextRange(propertiesOverride.Start, propertiesOverride.Length);
if (textPointer.End < text.Start)
if (textRange.End < text.Start)
{
continue;
}
if (textPointer.Start > text.End)
if (textRange.Start > text.End)
{
length = text.Length;
break;
}
if (textPointer.Start > text.Start)
if (textRange.Start > text.Start)
{
if (styleOverride.Style.TextFormat != currentTextStyle.TextFormat ||
!currentTextStyle.Foreground.Equals(styleOverride.Style.Foreground))
if (propertiesOverride.Value != currentProperties)
{
length = Math.Min(Math.Abs(textPointer.Start - text.Start), text.Length);
length = Math.Min(Math.Abs(textRange.Start - text.Start), text.Length);
break;
}
}
length += Math.Min(text.Length - length, textPointer.Length);
length += Math.Min(text.Length - length, textRange.Length);
if (hasOverride)
{
@ -370,13 +347,12 @@ namespace Avalonia.Media.TextFormatting
hasOverride = true;
currentTextStyle = styleOverride.Style;
currentProperties = propertiesOverride.Value;
}
if (length < text.Length && i == textStyleOverrides.Count)
if (length < text.Length && i == textModifier.Count)
{
if (currentTextStyle.Foreground.Equals(defaultTextStyle.Foreground) &&
currentTextStyle.TextFormat == defaultTextStyle.TextFormat)
if (currentProperties == defaultProperties)
{
length = text.Length;
}
@ -387,7 +363,7 @@ namespace Avalonia.Media.TextFormatting
text = text.Take(length);
}
return new TextStyleRun(new TextPointer(text.Start, length), currentTextStyle);
return new ValueSpan<TextRunProperties>(text.Start, length, currentProperties);
}
}
}

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

@ -1,5 +1,4 @@
using System.Collections.Generic;
using Avalonia.Platform;
namespace Avalonia.Media.TextFormatting
{
@ -9,12 +8,12 @@ namespace Avalonia.Media.TextFormatting
public abstract class TextLine
{
/// <summary>
/// Gets the text.
/// Gets the text range that is covered by the line.
/// </summary>
/// <value>
/// The text pointer.
/// The text range that is covered by the line.
/// </value>
public abstract TextPointer Text { get; }
public abstract TextRange TextRange { get; }
/// <summary>
/// Gets the text runs.
@ -32,12 +31,20 @@ namespace Avalonia.Media.TextFormatting
/// </value>
public abstract TextLineMetrics LineMetrics { get; }
/// <summary>
/// Gets the state of the line when broken by line breaking process.
/// </summary>
/// <returns>
/// A <see cref="LineBreak"/> value that represents the line break.
/// </returns>
public abstract TextLineBreak LineBreak { get; }
/// <summary>
/// Draws the <see cref="TextLine"/> at the given origin.
/// </summary>
/// <param name="drawingContext">The drawing context.</param>
/// <param name="origin">The origin.</param>
public abstract void Draw(IDrawingContextImpl drawingContext, Point origin);
public abstract void Draw(DrawingContext drawingContext, Point origin);
/// <summary>
/// Client to get the character hit corresponding to the specified

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

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace Avalonia.Media.TextFormatting
{
public class TextLineBreak
{
public TextLineBreak(IReadOnlyList<ShapedTextCharacters> remainingCharacters)
{
RemainingCharacters = remainingCharacters;
}
/// <summary>
/// Get the remaining shaped characters that were split up by the <see cref="TextFormatter"/> during the formatting process.
/// </summary>
public IReadOnlyList<ShapedTextCharacters> RemainingCharacters { get; }
}
}

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

@ -0,0 +1,235 @@
using System.Collections.Generic;
namespace Avalonia.Media.TextFormatting
{
internal class TextLineImpl : TextLine
{
private readonly IReadOnlyList<ShapedTextCharacters> _textRuns;
public TextLineImpl(IReadOnlyList<ShapedTextCharacters> textRuns, TextLineMetrics lineMetrics,
TextLineBreak lineBreak = null)
{
_textRuns = textRuns;
LineMetrics = lineMetrics;
LineBreak = lineBreak;
}
/// <inheritdoc/>
public override TextRange TextRange => LineMetrics.TextRange;
/// <inheritdoc/>
public override IReadOnlyList<TextRun> TextRuns => _textRuns;
/// <inheritdoc/>
public override TextLineMetrics LineMetrics { get; }
/// <inheritdoc/>
public override TextLineBreak LineBreak { get; }
/// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin)
{
var currentX = origin.X;
foreach (var textRun in _textRuns)
{
var baselineOrigin = new Point(currentX + LineMetrics.BaselineOrigin.X,
origin.Y + LineMetrics.BaselineOrigin.Y);
textRun.Draw(drawingContext, baselineOrigin);
currentX += textRun.Bounds.Width;
}
}
/// <inheritdoc/>
public override CharacterHit GetCharacterHitFromDistance(double distance)
{
if (distance < 0)
{
// hit happens before the line, return the first position
return new CharacterHit(TextRange.Start);
}
// process hit that happens within the line
var characterHit = new CharacterHit();
foreach (var run in _textRuns)
{
characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _);
if (distance <= run.Bounds.Width)
{
break;
}
distance -= run.Bounds.Width;
}
return characterHit;
}
/// <inheritdoc/>
public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{
return DistanceFromCodepointIndex(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0));
}
/// <inheritdoc/>
public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
{
if (TryFindNextCharacterHit(characterHit, out var nextCharacterHit))
{
return nextCharacterHit;
}
return new CharacterHit(TextRange.End); // Can't move, we're after the last character
}
/// <inheritdoc/>
public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
{
if (TryFindPreviousCharacterHit(characterHit, out var previousCharacterHit))
{
return previousCharacterHit;
}
return new CharacterHit(TextRange.Start); // Can't move, we're before the first character
}
/// <inheritdoc/>
public override CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit)
{
// same operation as move-to-previous
return GetPreviousCaretCharacterHit(characterHit);
}
/// <summary>
/// Get distance from line start to the specified codepoint index.
/// </summary>
private double DistanceFromCodepointIndex(int codepointIndex)
{
var currentDistance = 0.0;
foreach (var textRun in _textRuns)
{
if (codepointIndex > textRun.Text.End)
{
currentDistance += textRun.Bounds.Width;
continue;
}
return currentDistance + textRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(codepointIndex));
}
return currentDistance;
}
/// <summary>
/// Tries to find the next character hit.
/// </summary>
/// <param name="characterHit">The current character hit.</param>
/// <param name="nextCharacterHit">The next character hit.</param>
/// <returns></returns>
private bool TryFindNextCharacterHit(CharacterHit characterHit, out CharacterHit nextCharacterHit)
{
nextCharacterHit = characterHit;
var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (codepointIndex >= TextRange.Start + TextRange.Length)
{
return false; // Cannot go forward anymore
}
var runIndex = GetRunIndexAtCodepointIndex(codepointIndex);
while (runIndex < TextRuns.Count)
{
var run = _textRuns[runIndex];
nextCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
if (codepointIndex <= nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength)
{
return true;
}
runIndex++;
}
return false;
}
/// <summary>
/// Tries to find the previous character hit.
/// </summary>
/// <param name="characterHit">The current character hit.</param>
/// <param name="previousCharacterHit">The previous character hit.</param>
/// <returns></returns>
private bool TryFindPreviousCharacterHit(CharacterHit characterHit, out CharacterHit previousCharacterHit)
{
previousCharacterHit = characterHit;
var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (codepointIndex < TextRange.Start)
{
return false; // Cannot go backward anymore.
}
var runIndex = GetRunIndexAtCodepointIndex(codepointIndex);
while (runIndex >= 0)
{
var run = _textRuns[runIndex];
previousCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
if (previousCharacterHit.FirstCharacterIndex < codepointIndex)
{
return true;
}
runIndex--;
}
return false;
}
/// <summary>
/// Gets the run index of the specified codepoint index.
/// </summary>
/// <param name="codepointIndex">The codepoint index.</param>
/// <returns>The text run index.</returns>
private int GetRunIndexAtCodepointIndex(int codepointIndex)
{
if (codepointIndex >= TextRange.End)
{
return _textRuns.Count - 1;
}
if (codepointIndex <= 0)
{
return 0;
}
var runIndex = 0;
while (runIndex < _textRuns.Count)
{
var run = _textRuns[runIndex];
if (run.Text.End > codepointIndex)
{
return runIndex;
}
runIndex++;
}
return runIndex;
}
}
}

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

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
@ -8,38 +9,20 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public readonly struct TextLineMetrics
{
public TextLineMetrics(double width, double xOrigin, double ascent, double descent, double lineGap)
public TextLineMetrics(Size size, Point baselineOrigin, TextRange textRange)
{
Ascent = ascent;
Descent = descent;
LineGap = lineGap;
Size = new Size(width, descent - ascent + lineGap);
BaselineOrigin = new Point(xOrigin, -ascent);
Size = size;
BaselineOrigin = baselineOrigin;
TextRange = textRange;
}
/// <summary>
/// Gets the overall recommended distance above the baseline.
/// Gets the text range that is covered by the text line.
/// </summary>
/// <value>
/// The ascent.
/// The text range that is covered by the text line.
/// </value>
public double Ascent { get; }
/// <summary>
/// Gets the overall recommended distance under the baseline.
/// </summary>
/// <value>
/// The descent.
/// </value>
public double Descent { get; }
/// <summary>
/// Gets the overall recommended additional space between two lines of text.
/// </summary>
/// <value>
/// The leading.
/// </value>
public double LineGap { get; }
public TextRange TextRange { get; }
/// <summary>
/// Gets the size of the text line.
@ -61,10 +44,12 @@ namespace Avalonia.Media.TextFormatting
/// Creates the text line metrics.
/// </summary>
/// <param name="textRuns">The text runs.</param>
/// <param name="textRange">The text range that is covered by the text line.</param>
/// <param name="paragraphWidth">The paragraph width.</param>
/// <param name="textAlignment">The text alignment.</param>
/// <param name="paragraphProperties">The text alignment.</param>
/// <returns></returns>
public static TextLineMetrics Create(IEnumerable<TextRun> textRuns, double paragraphWidth, TextAlignment textAlignment)
public static TextLineMetrics Create(IEnumerable<TextRun> textRuns, TextRange textRange, double paragraphWidth,
TextParagraphProperties paragraphProperties)
{
var lineWidth = 0.0;
var ascent = 0.0;
@ -73,31 +58,39 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in textRuns)
{
var shapedRun = (ShapedTextRun)textRun;
var shapedRun = (ShapedTextCharacters)textRun;
lineWidth += shapedRun.Bounds.Width;
var fontMetrics =
new FontMetrics(shapedRun.Properties.Typeface, shapedRun.Properties.FontRenderingEmSize);
var textFormat = textRun.Style.TextFormat;
lineWidth += shapedRun.Bounds.Width;
if (ascent > textRun.Style.TextFormat.FontMetrics.Ascent)
if (ascent > fontMetrics.Ascent)
{
ascent = textFormat.FontMetrics.Ascent;
ascent = fontMetrics.Ascent;
}
if (descent < textFormat.FontMetrics.Descent)
if (descent < fontMetrics.Descent)
{
descent = textFormat.FontMetrics.Descent;
descent = fontMetrics.Descent;
}
if (lineGap < textFormat.FontMetrics.LineGap)
if (lineGap < fontMetrics.LineGap)
{
lineGap = textFormat.FontMetrics.LineGap;
lineGap = fontMetrics.LineGap;
}
}
var xOrigin = TextLine.GetParagraphOffsetX(lineWidth, paragraphWidth, textAlignment);
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(lineWidth, xOrigin, ascent, descent, lineGap);
return new TextLineMetrics(size, baselineOrigin, textRange);
}
}
}

33
src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs

@ -3,38 +3,37 @@
/// <summary>
/// Provides a set of properties that are used during the paragraph layout.
/// </summary>
public readonly struct TextParagraphProperties
public abstract class TextParagraphProperties
{
public TextParagraphProperties(
TextStyle defaultTextStyle,
TextAlignment textAlignment = TextAlignment.Left,
TextWrapping textWrapping = TextWrapping.NoWrap,
TextTrimming textTrimming = TextTrimming.None)
{
DefaultTextStyle = defaultTextStyle;
TextAlignment = textAlignment;
TextWrapping = textWrapping;
TextTrimming = textTrimming;
}
/// <summary>
/// Gets the text alignment.
/// </summary>
public abstract TextAlignment TextAlignment { get; }
/// <summary>
/// Gets the default text style.
/// </summary>
public TextStyle DefaultTextStyle { get; }
public abstract TextRunProperties DefaultTextRunProperties { get; }
/// <summary>
/// Gets the text alignment.
/// If not null, text decorations to apply to all runs in the line. This is in addition
/// to any text decorations specified by the TextRunProperties for individual text runs.
/// </summary>
public TextAlignment TextAlignment { get; }
public virtual TextDecorationCollection TextDecorations => null;
/// <summary>
/// Gets the text wrapping.
/// </summary>
public TextWrapping TextWrapping { get; }
public abstract TextWrapping TextWrapping { get; }
/// <summary>
/// Gets the text trimming.
/// </summary>
public TextTrimming TextTrimming { get; }
public abstract TextTrimming TextTrimming { get; }
/// <summary>
/// Paragraph's line height
/// </summary>
public abstract double LineHeight { get; }
}
}

16
src/Avalonia.Visuals/Media/TextFormatting/TextPointer.cs → src/Avalonia.Visuals/Media/TextFormatting/TextRange.cs

@ -5,9 +5,9 @@ namespace Avalonia.Media.TextFormatting
/// <summary>
/// References a portion of a text buffer.
/// </summary>
public readonly struct TextPointer
public readonly struct TextRange
{
public TextPointer(int start, int length)
public TextRange(int start, int length)
{
Start = start;
Length = length;
@ -41,30 +41,30 @@ namespace Avalonia.Media.TextFormatting
/// Returns a specified number of contiguous elements from the start of the slice.
/// </summary>
/// <param name="length">The number of elements to return.</param>
/// <returns>A <see cref="TextPointer"/> that contains the specified number of elements from the start of this slice.</returns>
public TextPointer Take(int length)
/// <returns>A <see cref="TextRange"/> that contains the specified number of elements from the start of this slice.</returns>
public TextRange Take(int length)
{
if (length > Length)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
return new TextPointer(Start, length);
return new TextRange(Start, length);
}
/// <summary>
/// Bypasses a specified number of elements in the slice and then returns the remaining elements.
/// </summary>
/// <param name="length">The number of elements to skip before returning the remaining elements.</param>
/// <returns>A <see cref="TextPointer"/> that contains the elements that occur after the specified index in this slice.</returns>
public TextPointer Skip(int length)
/// <returns>A <see cref="TextRange"/> that contains the elements that occur after the specified index in this slice.</returns>
public TextRange Skip(int length)
{
if (length > Length)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
return new TextPointer(Start + length, Length - length);
return new TextRange(Start + length, Length - length);
}
}
}

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

@ -1,5 +1,5 @@
using System.Diagnostics;
using Avalonia.Utility;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
@ -9,15 +9,22 @@ namespace Avalonia.Media.TextFormatting
[DebuggerTypeProxy(typeof(TextRunDebuggerProxy))]
public abstract class TextRun
{
public static readonly int DefaultTextSourceLength = 1;
/// <summary>
/// Gets the text source length.
/// </summary>
public virtual int TextSourceLength => DefaultTextSourceLength;
/// <summary>
/// Gets the text run's text.
/// </summary>
public ReadOnlySlice<char> Text { get; protected set; }
public virtual ReadOnlySlice<char> Text => default;
/// <summary>
/// Gets the text run's style.
/// A set of properties shared by every characters in the run
/// </summary>
public TextStyle Style { get; protected set; }
public virtual TextRunProperties Properties => null;
private class TextRunDebuggerProxy
{
@ -42,7 +49,7 @@ namespace Avalonia.Media.TextFormatting
}
}
public TextStyle Style => _textRun.Style;
public TextRunProperties Properties => _textRun.Properties;
}
}
}

90
src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs

@ -0,0 +1,90 @@
using System;
using System.Globalization;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Properties that can change from one run to the next, such as typeface or foreground brush.
/// </summary>
/// <remarks>
/// The client provides a concrete implementation of this abstract run properties class. This
/// allows client to implement their run properties the way that fits with their run formatting
/// store.
/// </remarks>
public abstract class TextRunProperties : IEquatable<TextRunProperties>
{
/// <summary>
/// Run typeface
/// </summary>
public abstract Typeface Typeface { get; }
/// <summary>
/// Em size of font used to format and display text
/// </summary>
public abstract double FontRenderingEmSize { get; }
///<summary>
/// Run TextDecorations.
///</summary>
public abstract TextDecorationCollection TextDecorations { get; }
/// <summary>
/// Brush used to fill text.
/// </summary>
public abstract IBrush ForegroundBrush { get; }
/// <summary>
/// Brush used to paint background of run.
/// </summary>
public abstract IBrush BackgroundBrush { get; }
/// <summary>
/// Run text culture.
/// </summary>
public abstract CultureInfo CultureInfo { get; }
public bool Equals(TextRunProperties other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return Typeface.Equals(other.Typeface) &&
FontRenderingEmSize.Equals(other.FontRenderingEmSize)
&& Equals(TextDecorations, other.TextDecorations) &&
Equals(ForegroundBrush, other.ForegroundBrush) &&
Equals(BackgroundBrush, other.BackgroundBrush) &&
Equals(CultureInfo, other.CultureInfo);
}
public override bool Equals(object obj)
{
return ReferenceEquals(this, obj) || obj is TextRunProperties other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (Typeface != null ? Typeface.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ FontRenderingEmSize.GetHashCode();
hashCode = (hashCode * 397) ^ (TextDecorations != null ? TextDecorations.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (ForegroundBrush != null ? ForegroundBrush.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (BackgroundBrush != null ? BackgroundBrush.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (CultureInfo != null ? CultureInfo.GetHashCode() : 0);
return hashCode;
}
}
public static bool operator ==(TextRunProperties left, TextRunProperties right)
{
return Equals(left, right);
}
public static bool operator !=(TextRunProperties left, TextRunProperties right)
{
return !Equals(left, right);
}
}
}

8
src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs

@ -1,6 +1,7 @@
using System;
using System.Globalization;
using Avalonia.Platform;
using Avalonia.Utility;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
@ -44,9 +45,10 @@ namespace Avalonia.Media.TextFormatting
}
/// <inheritdoc cref="ITextShaperImpl.ShapeText"/>
public GlyphRun ShapeText(ReadOnlySlice<char> text, TextFormat textFormat)
public GlyphRun ShapeText(ReadOnlySlice<char> text, Typeface typeface, double fontRenderingEmSize,
CultureInfo culture)
{
return _platformImpl.ShapeText(text, textFormat);
return _platformImpl.ShapeText(text, typeface, fontRenderingEmSize, culture);
}
}
}

39
src/Avalonia.Visuals/Media/TextFormatting/TextStyle.cs

@ -1,39 +0,0 @@
using Avalonia.Media.Immutable;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Unique text formatting properties that effect the styling of a text.
/// </summary>
public readonly struct TextStyle
{
public TextStyle(Typeface typeface, double fontRenderingEmSize = 12, IBrush foreground = null,
ImmutableTextDecoration[] textDecorations = null)
: this(new TextFormat(typeface, fontRenderingEmSize), foreground, textDecorations)
{
}
public TextStyle(TextFormat textFormat, IBrush foreground = null,
ImmutableTextDecoration[] textDecorations = null)
{
TextFormat = textFormat;
Foreground = foreground;
TextDecorations = textDecorations;
}
/// <summary>
/// Gets the text format.
/// </summary>
public TextFormat TextFormat { get; }
/// <summary>
/// Gets the foreground.
/// </summary>
public IBrush Foreground { get; }
/// <summary>
/// Gets the text decorations.
/// </summary>
public ImmutableTextDecoration[] TextDecorations { get; }
}
}

24
src/Avalonia.Visuals/Media/TextFormatting/TextStyleRun.cs

@ -1,24 +0,0 @@
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Represents a text run's style and is used during the layout process of the <see cref="TextFormatter"/>.
/// </summary>
public readonly struct TextStyleRun
{
public TextStyleRun(TextPointer textPointer, TextStyle style)
{
TextPointer = textPointer;
Style = style;
}
/// <summary>
/// Gets the text pointer.
/// </summary>
public TextPointer TextPointer { get; }
/// <summary>
/// Gets the text style.
/// </summary>
public TextStyle Style { get; }
}
}

2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs

@ -1,4 +1,4 @@
using Avalonia.Utility;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{

2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/CodepointEnumerator.cs

@ -1,4 +1,4 @@
using Avalonia.Utility;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{

2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/Grapheme.cs

@ -1,4 +1,4 @@
using Avalonia.Utility;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{

2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeEnumerator.cs

@ -4,7 +4,7 @@
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System.Runtime.InteropServices;
using Avalonia.Utility;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{

2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs

@ -16,7 +16,7 @@
// Ported from: https://github.com/foliojs/linebreak
// Copied from: https://github.com/toptensoftware/RichTextKit
using Avalonia.Utility;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{

9
src/Avalonia.Visuals/Media/TextWrapping.cs

@ -5,6 +5,13 @@ namespace Avalonia.Media
/// </summary>
public enum TextWrapping
{
/// <summary>
/// Line-breaking occurs if the line overflows the available block width.
/// However, a line may overflow the block width if the line breaking algorithm
/// cannot determine a break opportunity, as in the case of a very long word.
/// </summary>
WrapWithOverflow,
/// <summary>
/// Text should not wrap.
/// </summary>
@ -15,4 +22,4 @@ namespace Avalonia.Media
/// </summary>
Wrap
}
}
}

12
src/Avalonia.Visuals/Media/Typeface.cs

@ -16,11 +16,11 @@ namespace Avalonia.Media
/// Initializes a new instance of the <see cref="Typeface"/> class.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <param name="weight">The font weight.</param>
/// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param>
public Typeface([NotNull]FontFamily fontFamily,
FontWeight weight = FontWeight.Normal,
FontStyle style = FontStyle.Normal)
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal)
{
if (weight <= 0)
{
@ -39,9 +39,9 @@ namespace Avalonia.Media
/// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param>
public Typeface(string fontFamilyName,
FontWeight weight = FontWeight.Normal,
FontStyle style = FontStyle.Normal)
: this(new FontFamily(fontFamilyName), weight, style)
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal)
: this(new FontFamily(fontFamilyName), style, weight)
{
}

12
src/Avalonia.Visuals/Platform/ITextShaperImpl.cs

@ -1,6 +1,6 @@
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Utility;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Platform
{
@ -13,8 +13,10 @@ namespace Avalonia.Platform
/// Shapes the specified region within the text and returns a resulting glyph run.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="textFormat">The text format.</param>
/// <param name="typeface">The typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="culture">The culture.</param>
/// <returns>A shaped glyph run.</returns>
GlyphRun ShapeText(ReadOnlySlice<char> text, TextFormat textFormat);
GlyphRun ShapeText(ReadOnlySlice<char> text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture);
}
}

5
src/Avalonia.Visuals/Utility/ReadOnlySlice.cs → src/Avalonia.Visuals/Utilities/ReadOnlySlice.cs

@ -2,9 +2,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Utilities;
namespace Avalonia.Utility
namespace Avalonia.Utilities
{
/// <summary>
/// ReadOnlySlice enables the ability to work with a sequence within a region of memory and retains the position in within that region.
@ -47,7 +46,7 @@ namespace Avalonia.Utility
public int Length { get; }
/// <summary>
/// Gets a value that indicates whether this instance of <see cref="ReadOnlySpan{T}"/> is Empty.
/// Gets a value that indicates whether this instance of <see cref="ReadOnlySlice{T}"/> is Empty.
/// </summary>
public bool IsEmpty => Length == 0;

30
src/Avalonia.Visuals/Utilities/ValueSpan.cs

@ -0,0 +1,30 @@
namespace Avalonia.Utilities
{
/// <summary>
/// Pairing of value and positions sharing that value.
/// </summary>
public readonly struct ValueSpan<T>
{
public ValueSpan(int start, int length, T value)
{
Start = start;
Length = length;
Value = value;
}
/// <summary>
/// Get's the start of the span.
/// </summary>
public int Start { get; }
/// <summary>
/// Get's the length of the span.
/// </summary>
public int Length { get; }
/// <summary>
/// Get's the value of the span.
/// </summary>
public T Value { get; }
}
}

6
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLIL_INTERNAL</DefineConstants>
<DefineConstants>PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLX_INTERNAL</DefineConstants>
<UseCecil>false</UseCecil>
<DefineConstants Condition="$(UseCecil) == true">$(DefineConstants);RUNTIME_XAML_CECIL</DefineConstants>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
@ -77,8 +77,8 @@
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
<Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
<Compile Include="XamlLoadException.cs" />
<Compile Include="XamlIl\xamlil.github\src\XamlIl\**\*.cs" />
<Compile Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs" />
<Compile Include="XamlIl\xamlil.github\src\XamlX\**\*.cs" />
<Compile Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlX.Il.Cecil\**\*.cs" />
<Compile Remove="XamlIl\xamlil.github\**\obj\**\*.cs" />
<Compile Include="..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" />
<Compile Include="..\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs" />

34
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs

@ -10,12 +10,15 @@ using System.Runtime.InteropServices;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Platform;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Transform;
using XamlX.TypeSystem;
using XamlX.IL;
using XamlX.Emit;
#if RUNTIME_XAML_CECIL
using TypeAttributes = Mono.Cecil.TypeAttributes;
using Mono.Cecil;
using XamlIl.Ast;
using XamlX.Ast;
using XamlX.IL.Cecil;
#endif
namespace Avalonia.Markup.Xaml.XamlIl
{
@ -24,9 +27,10 @@ namespace Avalonia.Markup.Xaml.XamlIl
#if !RUNTIME_XAML_CECIL
private static SreTypeSystem _sreTypeSystem;
private static ModuleBuilder _sreBuilder;
private static IXamlIlType _sreContextType;
private static XamlIlLanguageTypeMappings _sreMappings;
private static XamlIlXmlnsMappings _sreXmlns;
private static IXamlType _sreContextType;
private static XamlLanguageTypeMappings _sreMappings;
private static XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> _sreEmitMappings;
private static XamlXmlnsMappings _sreXmlns;
private static AssemblyBuilder _sreAsm;
private static bool _sreCanSave;
@ -82,13 +86,14 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
if (_sreMappings == null)
_sreMappings = AvaloniaXamlIlLanguage.Configure(_sreTypeSystem);
(_sreMappings, _sreEmitMappings) = AvaloniaXamlIlLanguage.Configure(_sreTypeSystem);
if (_sreXmlns == null)
_sreXmlns = XamlIlXmlnsMappings.Resolve(_sreTypeSystem, _sreMappings);
_sreXmlns = XamlXmlnsMappings.Resolve(_sreTypeSystem, _sreMappings);
if (_sreContextType == null)
_sreContextType = XamlIlContextDefinition.GenerateContextClass(
_sreContextType = XamlILContextDefinition.GenerateContextClass(
_sreTypeSystem.CreateTypeBuilder(
_sreBuilder.DefineType("XamlIlContext")), _sreTypeSystem, _sreMappings);
_sreBuilder.DefineType("XamlIlContext")), _sreTypeSystem, _sreMappings,
_sreEmitMappings);
}
@ -122,10 +127,11 @@ namespace Avalonia.Markup.Xaml.XamlIl
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType))),
_sreEmitMappings,
_sreContextType) { EnableIlVerification = true };
IXamlIlType overrideType = null;
IXamlType overrideType = null;
if (rootInstance != null)
{
overrideType = _sreTypeSystem.GetType(rootInstance.GetType());
@ -209,6 +215,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
private static string _cecilEmitDir;
private static CecilTypeSystem _cecilTypeSystem;
private static XamlIlLanguageTypeMappings _cecilMappings;
private static XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> _cecilEmitMappings;
private static XamlIlXmlnsMappings _cecilXmlns;
private static bool _cecilInitialized;
@ -221,7 +228,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
Directory.CreateDirectory(_cecilEmitDir);
var refs = new[] {path}.Concat(File.ReadAllLines(path + ".refs"));
_cecilTypeSystem = new CecilTypeSystem(refs);
_cecilMappings = AvaloniaXamlIlLanguage.Configure(_cecilTypeSystem);
(_cecilMappings, _cecilEmitMappings) = AvaloniaXamlIlLanguage.Configure(_cecilTypeSystem);
_cecilXmlns = XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings);
_cecilInitialized = true;
}
@ -232,7 +239,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
if (uri == null)
throw new InvalidOperationException("Please, go away");
InitializeCecil();
IXamlIlType overrideType = null;
IXamlType overrideType = null;
if (rootInstance != null)
{
overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName);
@ -267,6 +274,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
localAssembly == null ? null : _cecilTypeSystem.FindAssembly(localAssembly.GetName().Name),
_cecilMappings, XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings),
AvaloniaXamlIlLanguage.CustomValueConverter),
_cecilEmitMappings,
_cecilTypeSystem.CreateTypeBuilder(contextDef));
compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType);
var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll");

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

@ -1,30 +1,33 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Parsers;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Parsers;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class AvaloniaXamlIlCompiler : XamlIlCompiler
class AvaloniaXamlIlCompiler : XamlILCompiler
{
private readonly XamlIlTransformerConfiguration _configuration;
private readonly IXamlIlType _contextType;
private readonly TransformerConfiguration _configuration;
private readonly IXamlType _contextType;
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
private AvaloniaXamlIlCompiler(AvaloniaXamlIlCompilerConfiguration configuration) : base(configuration, true)
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
: base(configuration, emitMappings, true)
{
_configuration = configuration;
void InsertAfter<T>(params IXamlIlAstTransformer[] t)
void InsertAfter<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t);
void InsertBefore<T>(params IXamlIlAstTransformer[] t)
void InsertBefore<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T), t);
@ -38,14 +41,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
// Targeted
InsertBefore<XamlIlPropertyReferenceResolver>(
InsertBefore<PropertyReferenceResolver>(
new AvaloniaXamlIlTransformInstanceAttachedProperties(),
new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
InsertAfter<XamlIlPropertyReferenceResolver>(new AvaloniaXamlIlAvaloniaPropertyResolver());
InsertAfter<PropertyReferenceResolver>(new AvaloniaXamlIlAvaloniaPropertyResolver());
InsertBefore<XamlIlContentConvertTransformer>(
InsertBefore<ContentConvertTransformer>(
new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
@ -56,8 +59,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
);
// After everything else
InsertBefore<XamlIlNewObjectTransformer>(
InsertBefore<NewObjectTransformer>(
new AddNameScopeRegistration(),
new AvaloniaXamlIlDataContextTypeTransformer(),
new AvaloniaXamlIlBindingPathTransformer(),
@ -66,17 +68,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
Transformers.Add(new AvaloniaXamlIlRootObjectScope());
}
public AvaloniaXamlIlCompiler(AvaloniaXamlIlCompilerConfiguration configuration,
IXamlIlTypeBuilder contextTypeBuilder) : this(configuration)
Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter());
}
public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings,
IXamlTypeBuilder<IXamlILEmitter> contextTypeBuilder)
: this(configuration, emitMappings)
{
_contextType = CreateContextType(contextTypeBuilder);
}
public AvaloniaXamlIlCompiler(AvaloniaXamlIlCompilerConfiguration configuration,
IXamlIlType contextType) : this(configuration)
public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings,
IXamlType contextType) : this(configuration, emitMappings)
{
_contextType = contextType;
}
@ -96,37 +103,35 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
set => _bindingTransformer.CompileBindingsByDefault = value;
}
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlIlTypeBuilder tb, IXamlIlType overrideRootType)
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder<IXamlILEmitter> tb, IXamlType overrideRootType)
{
var parsed = XDocumentXamlIlParser.Parse(xaml, new Dictionary<string, string>
var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
{
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
});
var rootObject = (XamlIlAstObjectNode)parsed.Root;
var rootObject = (XamlAstObjectNode)parsed.Root;
var classDirective = rootObject.Children
.OfType<XamlIlAstXmlDirective>().FirstOrDefault(x =>
.OfType<XamlAstXmlDirective>().FirstOrDefault(x =>
x.Namespace == XamlNamespaces.Xaml2006
&& x.Name == "Class");
var rootType =
classDirective != null ?
new XamlIlAstClrTypeReference(classDirective,
_configuration.TypeSystem.GetType(((XamlIlAstTextNode)classDirective.Values[0]).Text),
new XamlAstClrTypeReference(classDirective,
_configuration.TypeSystem.GetType(((XamlAstTextNode)classDirective.Values[0]).Text),
false) :
XamlIlTypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
(XamlIlAstXmlTypeReference)rootObject.Type, true);
TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
(XamlAstXmlTypeReference)rootObject.Type, true);
if (overrideRootType != null)
{
if (!rootType.Type.IsAssignableFrom(overrideRootType))
throw new XamlIlLoadException(
throw new XamlX.XamlLoadException(
$"Unable to substitute {rootType.Type.GetFqn()} with {overrideRootType.GetFqn()}", rootObject);
rootType = new XamlIlAstClrTypeReference(rootObject, overrideRootType, false);
rootType = new XamlAstClrTypeReference(rootObject, overrideRootType, false);
}
OverrideRootType(parsed, rootType);
@ -136,17 +141,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
public void OverrideRootType(XamlIlDocument doc, IXamlIlAstTypeReference newType)
public void OverrideRootType(XamlDocument doc, IXamlAstTypeReference newType)
{
var root = (XamlIlAstObjectNode)doc.Root;
var root = (XamlAstObjectNode)doc.Root;
var oldType = root.Type;
if (oldType.Equals(newType))
return;
root.Type = newType;
foreach (var child in root.Children.OfType<XamlIlAstXamlPropertyValueNode>())
foreach (var child in root.Children.OfType<XamlAstXamlPropertyValueNode>())
{
if (child.Property is XamlIlAstNamePropertyReference prop)
if (child.Property is XamlAstNamePropertyReference prop)
{
if (prop.DeclaringType.Equals(oldType))
prop.DeclaringType = newType;

16
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs

@ -1,18 +1,18 @@
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class AvaloniaXamlIlCompilerConfiguration : XamlIlTransformerConfiguration
class AvaloniaXamlIlCompilerConfiguration : TransformerConfiguration
{
public XamlIlClrPropertyInfoEmitter ClrPropertyEmitter { get; }
public XamlIlPropertyInfoAccessorFactoryEmitter AccessorFactoryEmitter { get; }
public AvaloniaXamlIlCompilerConfiguration(IXamlIlTypeSystem typeSystem,
IXamlIlAssembly defaultAssembly,
XamlIlLanguageTypeMappings typeMappings,
XamlIlXmlnsMappings xmlnsMappings,
XamlIlValueConverter customValueConverter,
public AvaloniaXamlIlCompilerConfiguration(IXamlTypeSystem typeSystem,
IXamlAssembly defaultAssembly,
XamlLanguageTypeMappings typeMappings,
XamlXmlnsMappings xmlnsMappings,
XamlValueConverter customValueConverter,
XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter)
: base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter)

78
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -2,10 +2,12 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
@ -17,12 +19,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class AvaloniaXamlIlLanguage
{
public static XamlIlLanguageTypeMappings Configure(IXamlIlTypeSystem typeSystem)
public static (XamlLanguageTypeMappings language, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emit) Configure(IXamlTypeSystem typeSystem)
{
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var assignBindingAttribute = typeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
var bindingType = typeSystem.GetType("Avalonia.Data.IBinding");
var rv = new XamlIlLanguageTypeMappings(typeSystem)
var rv = new XamlLanguageTypeMappings(typeSystem)
{
SupportInitialize = typeSystem.GetType("System.ComponentModel.ISupportInitialize"),
XmlnsAttributes =
@ -51,18 +53,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
},
InnerServiceProviderFactoryMethod =
runtimeHelpers.FindMethod(m => m.Name == "CreateInnerServiceProviderV1"),
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.EmitProvideValueTarget,
};
rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv);
rv.ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c);
return rv;
var emit = new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>
{
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.EmitProvideValueTarget,
ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c)
};
return (rv, emit);
}
public const string ContextNameScopeFieldName = "AvaloniaNameScope";
private static void EmitNameScopeField(XamlIlLanguageTypeMappings mappings,
IXamlIlTypeSystem typeSystem,
IXamlIlTypeBuilder typebuilder, IXamlIlEmitter constructor)
private static void EmitNameScopeField(XamlLanguageTypeMappings mappings,
IXamlTypeSystem typeSystem,
IXamlTypeBuilder<IXamlILEmitter> typebuilder, IXamlILEmitter constructor)
{
var nameScopeType = typeSystem.FindType("Avalonia.Controls.INameScope");
@ -78,23 +84,23 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
class AttributeResolver : IXamlIlCustomAttributeResolver
class AttributeResolver : IXamlCustomAttributeResolver
{
private readonly IXamlIlType _typeConverterAttribute;
private readonly IXamlType _typeConverterAttribute;
private readonly List<KeyValuePair<IXamlIlType, IXamlIlType>> _converters =
new List<KeyValuePair<IXamlIlType, IXamlIlType>>();
private readonly List<KeyValuePair<IXamlType, IXamlType>> _converters =
new List<KeyValuePair<IXamlType, IXamlType>>();
private readonly IXamlIlType _avaloniaList;
private readonly IXamlIlType _avaloniaListConverter;
private readonly IXamlType _avaloniaList;
private readonly IXamlType _avaloniaListConverter;
public AttributeResolver(IXamlIlTypeSystem typeSystem, XamlIlLanguageTypeMappings mappings)
public AttributeResolver(IXamlTypeSystem typeSystem, XamlLanguageTypeMappings mappings)
{
_typeConverterAttribute = mappings.TypeConverterAttributes.First();
void AddType(IXamlIlType type, IXamlIlType conv)
=> _converters.Add(new KeyValuePair<IXamlIlType, IXamlIlType>(type, conv));
void AddType(IXamlType type, IXamlType conv)
=> _converters.Add(new KeyValuePair<IXamlType, IXamlType>(type, conv));
void Add(string type, string conv)
=> AddType(typeSystem.GetType(type), typeSystem.GetType(conv));
@ -113,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
_avaloniaListConverter = typeSystem.GetType("Avalonia.Collections.AvaloniaListConverter`1");
}
IXamlIlType LookupConverter(IXamlIlType type)
IXamlType LookupConverter(IXamlType type)
{
foreach(var p in _converters)
if (p.Key.Equals(type))
@ -123,15 +129,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return null;
}
class ConstructedAttribute : IXamlIlCustomAttribute
class ConstructedAttribute : IXamlCustomAttribute
{
public bool Equals(IXamlIlCustomAttribute other) => false;
public bool Equals(IXamlCustomAttribute other) => false;
public IXamlIlType Type { get; }
public IXamlType Type { get; }
public List<object> Parameters { get; }
public Dictionary<string, object> Properties { get; }
public ConstructedAttribute(IXamlIlType type, List<object> parameters, Dictionary<string, object> properties)
public ConstructedAttribute(IXamlType type, List<object> parameters, Dictionary<string, object> properties)
{
Type = type;
Parameters = parameters ?? new List<object>();
@ -139,7 +145,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlType type, IXamlIlType attributeType)
public IXamlCustomAttribute GetCustomAttribute(IXamlType type, IXamlType attributeType)
{
if (attributeType.Equals(_typeConverterAttribute))
{
@ -151,25 +157,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return null;
}
public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlProperty property, IXamlIlType attributeType)
public IXamlCustomAttribute GetCustomAttribute(IXamlProperty property, IXamlType attributeType)
{
return null;
}
}
public static bool CustomValueConverter(XamlIlAstTransformationContext context,
IXamlIlAstValueNode node, IXamlIlType type, out IXamlIlAstValueNode result)
public static bool CustomValueConverter(AstTransformationContext context,
IXamlAstValueNode node, IXamlType type, out IXamlAstValueNode result)
{
if (type.FullName == "System.TimeSpan"
&& node is XamlIlAstTextNode tn
&& node is XamlAstTextNode tn
&& !tn.Text.Contains(":"))
{
var seconds = double.Parse(tn.Text, CultureInfo.InvariantCulture);
result = new XamlIlStaticOrTargetedReturnMethodCallNode(tn,
result = new XamlStaticOrTargetedReturnMethodCallNode(tn,
type.FindMethod("FromSeconds", type, false, context.Configuration.WellKnownTypes.Double),
new[]
{
new XamlIlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds)
new XamlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds)
});
return true;
}
@ -178,9 +184,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
var scope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
if (scope == null)
throw new XamlIlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node);
if (!(node is XamlIlAstTextNode text))
throw new XamlIlLoadException("Property should be a text node", node);
throw new XamlX.XamlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node);
if (!(node is XamlAstTextNode text))
throw new XamlX.XamlLoadException("Property should be a text node", node);
result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text);
return true;
}

96
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs

@ -1,35 +1,37 @@
using System;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
using XamlX.Emit;
using XamlX.IL;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AddNameScopeRegistration : IXamlIlAstTransformer
class AddNameScopeRegistration : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlPropertyAssignmentNode pa)
if (node is XamlPropertyAssignmentNode pa)
{
if (pa.Property.Name == "Name"
&& pa.Property.DeclaringType.FullName == "Avalonia.StyledElement")
{
if (context.ParentNodes().FirstOrDefault() is XamlIlManipulationGroupNode mg
if (context.ParentNodes().FirstOrDefault() is XamlManipulationGroupNode mg
&& mg.Children.OfType<AvaloniaNameScopeRegistrationXamlIlNode>().Any())
return node;
IXamlIlAstValueNode value = null;
IXamlAstValueNode value = null;
for (var c = 0; c < pa.Values.Count; c++)
if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String))
{
value = pa.Values[c];
if (!(value is XamlIlAstTextNode))
if (!(value is XamlAstTextNode))
{
var local = new XamlIlAstCompilerLocalNode(value);
var local = new XamlAstCompilerLocalNode(value);
// Wrap original in local initialization
pa.Values[c] = new XamlIlAstLocalInitializationNodeEmitter(value, value, local);
pa.Values[c] = new XamlAstLocalInitializationNodeEmitter(value, value, local);
// Use local
value = local;
}
@ -39,13 +41,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (value != null)
{
var objectType = context.ParentNodes().OfType<XamlIlAstObjectNode>().FirstOrDefault()?.Type.GetClrType();
return new XamlIlManipulationGroupNode(pa)
var objectType = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault()?.Type.GetClrType();
return new XamlManipulationGroupNode(pa)
{
Children =
{
pa,
new AvaloniaNameScopeRegistrationXamlIlNode(value, context.GetAvaloniaTypes(), objectType)
new AvaloniaNameScopeRegistrationXamlIlNode(value, objectType)
}
};
}
@ -61,52 +63,58 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
class NestedScopeMetadataNode : XamlIlValueWithSideEffectNodeBase
class NestedScopeMetadataNode : XamlValueWithSideEffectNodeBase
{
public NestedScopeMetadataNode(IXamlIlAstValueNode value) : base(value, value)
public NestedScopeMetadataNode(IXamlAstValueNode value) : base(value, value)
{
}
}
class AvaloniaNameScopeRegistrationXamlIlNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
class AvaloniaNameScopeRegistrationXamlIlNode : XamlAstNode, IXamlAstManipulationNode
{
private readonly AvaloniaXamlIlWellKnownTypes _types;
public IXamlIlAstValueNode Name { get; set; }
public IXamlIlType TargetType { get; }
public IXamlAstValueNode Name { get; set; }
public IXamlType TargetType { get; }
public AvaloniaNameScopeRegistrationXamlIlNode(IXamlIlAstValueNode name, AvaloniaXamlIlWellKnownTypes types, IXamlIlType targetType) : base(name)
public AvaloniaNameScopeRegistrationXamlIlNode(IXamlAstValueNode name, IXamlType targetType) : base(name)
{
_types = types;
TargetType = targetType;
Name = name;
}
public override void VisitChildren(IXamlIlAstVisitor visitor)
=> Name = (IXamlIlAstValueNode)Name.Visit(visitor);
public override void VisitChildren(IXamlAstVisitor visitor)
=> Name = (IXamlAstValueNode)Name.Visit(visitor);
}
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
class AvaloniaNameScopeRegistrationXamlIlNodeEmitter : IXamlAstLocalsNodeEmitter<IXamlILEmitter, XamlILNodeEmitResult>
{
public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var targetLoc = context.GetLocal(context.Configuration.WellKnownTypes.Object))
if (node is AvaloniaNameScopeRegistrationXamlIlNode registration)
{
codeGen
// var target = {pop}
.Stloc(targetLoc.Local)
// _context.NameScope.Register(Name, target)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField);
context.Emit(Name, codeGen, Name.Type.GetClrType());
codeGen
.Ldloc(targetLoc.Local)
.EmitCall(_types.INameScopeRegister, true);
}
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var targetLoc = context.GetLocalOfType(context.Configuration.WellKnownTypes.Object))
{
return XamlIlNodeEmitResult.Void(1);
codeGen
// var target = {pop}
.Stloc(targetLoc.Local)
// _context.NameScope.Register(Name, target)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField);
context.Emit(registration.Name, codeGen, registration.Name.Type.GetClrType());
codeGen
.Ldloc(targetLoc.Local)
.EmitCall(context.GetAvaloniaTypes().INameScopeRegister, true);
}
return XamlILNodeEmitResult.Void(1);
}
return default;
}
}
}

30
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs

@ -1,35 +1,38 @@
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlParseException = XamlX.XamlParseException;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaBindingExtensionTransformer : IXamlIlAstTransformer
class AvaloniaBindingExtensionTransformer : IXamlAstTransformer
{
public bool CompileBindingsByDefault { get; set; }
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlCompileBindingsNode)
{
return node;
}
if (node is XamlIlAstObjectNode obj)
if (node is XamlAstObjectNode obj)
{
foreach (var item in obj.Children)
{
if (item is XamlIlAstXmlDirective directive)
if (item is XamlAstXmlDirective directive)
{
if (directive.Namespace == XamlNamespaces.Xaml2006
&& directive.Name == "CompileBindings"
&& directive.Values.Count == 1)
{
if (!(directive.Values[0] is XamlIlAstTextNode text
if (!(directive.Values[0] is XamlAstTextNode text
&& bool.TryParse(text.Text, out var compileBindings)))
{
throw new XamlIlParseException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
throw new XamlParseException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
}
obj.Children.Remove(directive);
@ -40,10 +43,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
// Our code base expects XAML parser to prefer `FooExtension` to `Foo` even with `<Foo>` syntax
// This is the legacy of Portable.Xaml, so we emulate that behavior here
// Convert the <Binding> tag to either a CompiledBinding or ReflectionBinding tag.
if (node is XamlIlAstXmlTypeReference tref
if (node is XamlAstXmlTypeReference tref
&& tref.Name == "Binding"
&& tref.XmlNamespace == "https://github.com/avaloniaui")
{
@ -60,9 +62,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
internal class AvaloniaXamlIlCompileBindingsNode : XamlIlValueWithSideEffectNodeBase
internal class AvaloniaXamlIlCompileBindingsNode : XamlValueWithSideEffectNodeBase
{
public AvaloniaXamlIlCompileBindingsNode(IXamlIlAstValueNode value, bool compileBindings)
public AvaloniaXamlIlCompileBindingsNode(IXamlAstValueNode value, bool compileBindings)
: base(value, value)
{
CompileBindings = compileBindings;

10
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs

@ -1,14 +1,14 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlAvaloniaPropertyResolver : IXamlIlAstTransformer
class AvaloniaXamlIlAvaloniaPropertyResolver : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstClrProperty prop)
if (node is XamlAstClrProperty prop)
{
var n = prop.Name + "Property";
var field =

114
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs

@ -4,23 +4,25 @@ using System.Linq;
using System.Text;
using Avalonia.Markup.Parsers;
using Avalonia.Utilities;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
using XamlParseException = XamlX.XamlParseException;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlBindingPathParser : IXamlIlAstTransformer
class AvaloniaXamlIlBindingPathParser : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
if (node is XamlAstObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
{
var convertedNode = ConvertLongFormPropertiesToBindingExpressionNode(context, binding);
if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlIlAstTextNode bindingPathText)
if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlAstTextNode bindingPathText)
{
var reader = new CharacterReader(bindingPathText.Text.AsSpan());
var (nodes, _) = BindingExpressionGrammar.Parse(ref reader);
@ -34,10 +36,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else
{
var bindingPathAssignment = binding.Children.OfType<XamlIlAstXamlPropertyValueNode>()
var bindingPathAssignment = binding.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(v => v.Property.GetClrProperty().Name == "Path");
if (bindingPathAssignment != null && bindingPathAssignment.Values[0] is XamlIlAstTextNode pathValue)
if (bindingPathAssignment != null && bindingPathAssignment.Values[0] is XamlAstTextNode pathValue)
{
var reader = new CharacterReader(pathValue.Text.AsSpan());
var (nodes, _) = BindingExpressionGrammar.Parse(ref reader);
@ -56,12 +58,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
private static BindingExpressionGrammar.INode ConvertLongFormPropertiesToBindingExpressionNode(
XamlIlAstTransformationContext context,
XamlIlAstObjectNode binding)
AstTransformationContext context,
XamlAstObjectNode binding)
{
BindingExpressionGrammar.INode convertedNode = null;
var syntheticCompiledBindingProperties = binding.Children.OfType<XamlIlAstXamlPropertyValueNode>()
var syntheticCompiledBindingProperties = binding.Children.OfType<XamlAstXamlPropertyValueNode>()
.Where(v => v.Property is AvaloniaSyntheticCompiledBindingProperty)
.ToList();
@ -80,20 +82,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
v.Property is AvaloniaSyntheticCompiledBindingProperty prop
&& prop.Name == SyntheticCompiledBindingPropertyName.RelativeSource);
if (elementNameProperty?.Values[0] is XamlIlAstTextNode elementName)
if (elementNameProperty?.Values[0] is XamlAstTextNode elementName)
{
convertedNode = new BindingExpressionGrammar.NameNode { Name = elementName.Text };
}
else if (elementNameProperty != null)
{
throw new XamlIlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
throw new XamlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
}
if (sourceProperty?.Values[0] != null)
{
if (convertedNode != null)
{
throw new XamlIlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
}
convertedNode = new RawSourceBindingExpressionNode(sourceProperty?.Values[0]);
@ -106,13 +108,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (convertedNode != null)
{
throw new XamlIlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
}
var mode = relativeSourceObject.Children
.OfType<XamlIlAstXamlPropertyValueNode>()
.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Mode")
?.Values[0] is XamlIlAstTextNode modeAssignedValue ? modeAssignedValue.Text : null;
?.Values[0] is XamlAstTextNode modeAssignedValue ? modeAssignedValue.Text : null;
if (relativeSourceObject.Arguments.Count == 0 && mode == null)
{
mode = "FindAncestor";
@ -121,46 +123,46 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (mode == "FindAncestor")
{
var ancestorLevel = relativeSourceObject.Children
.OfType<XamlIlAstXamlPropertyValueNode>()
.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "FindAncestor")
?.Values[0] is XamlIlAstTextNode ancestorLevelText ? int.Parse(ancestorLevelText.Text) - 1 : 0;
?.Values[0] is XamlAstTextNode ancestorLevelText ? int.Parse(ancestorLevelText.Text) - 1 : 0;
var treeType = relativeSourceObject.Children
.OfType<XamlIlAstXamlPropertyValueNode>()
.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Tree")
?.Values[0] is XamlIlAstTextNode treeTypeValue ? treeTypeValue.Text : "Visual";
?.Values[0] is XamlAstTextNode treeTypeValue ? treeTypeValue.Text : "Visual";
var ancestorTypeName = relativeSourceObject.Children
.OfType<XamlIlAstXamlPropertyValueNode>()
.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "AncestorType")
?.Values[0] as XamlIlAstTextNode;
?.Values[0] as XamlAstTextNode;
IXamlIlType ancestorType = null;
IXamlType ancestorType = null;
if (ancestorTypeName is null)
{
if (treeType == "Visual")
{
throw new XamlIlParseException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
throw new XamlParseException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
}
else if (treeType == "Logical")
{
var styledElementType = context.GetAvaloniaTypes().StyledElement;
ancestorType = context
.ParentNodes()
.OfType<XamlIlAstObjectNode>()
.OfType<XamlAstObjectNode>()
.Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType()))
.ElementAtOrDefault(ancestorLevel)
?.Type.GetClrType();
if (ancestorType is null)
{
throw new XamlIlParseException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
}
}
}
else
{
ancestorType = XamlIlTypeReferenceResolver.ResolveType(
ancestorType = TypeReferenceResolver.ResolveType(
context,
ancestorTypeName.Text,
false,
@ -186,7 +188,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else
{
throw new XamlIlParseException($"Unknown tree type '{treeType}'.", binding);
throw new XamlParseException($"Unknown tree type '{treeType}'.", binding);
}
}
else if (mode == "DataContext")
@ -206,14 +208,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (parentType is null)
{
throw new XamlIlParseException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
throw new XamlParseException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
}
convertedNode = new TemplatedParentBindingExpressionNode { Type = parentType };
}
else
{
throw new XamlIlParseException($"Unknown RelativeSource mode '{mode}'.", binding);
throw new XamlParseException($"Unknown RelativeSource mode '{mode}'.", binding);
}
}
@ -234,9 +236,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
private static bool GetRelativeSourceObjectFromAssignment(
XamlIlAstTransformationContext context,
XamlIlAstXamlPropertyValueNode relativeSourceProperty,
out XamlIlAstObjectNode relativeSourceObject)
AstTransformationContext context,
XamlAstXamlPropertyValueNode relativeSourceProperty,
out XamlAstObjectNode relativeSourceObject)
{
relativeSourceObject = null;
if (relativeSourceProperty is null)
@ -244,22 +246,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return false;
}
if (relativeSourceProperty.Values[0] is XamlIlMarkupExtensionNode me)
if (relativeSourceProperty.Values[0] is XamlMarkupExtensionNode me)
{
if (me.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
{
throw new XamlIlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
}
relativeSourceObject = (XamlIlAstObjectNode)me.Value;
relativeSourceObject = (XamlAstObjectNode)me.Value;
return true;
}
if (relativeSourceProperty.Values[0] is XamlIlAstObjectNode on)
if (relativeSourceProperty.Values[0] is XamlAstObjectNode on)
{
if (on.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
{
throw new XamlIlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
}
relativeSourceObject = on;
@ -270,24 +272,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
class ParsedBindingPathNode : XamlIlAstNode, IXamlIlAstValueNode
class ParsedBindingPathNode : XamlAstNode, IXamlAstValueNode
{
public ParsedBindingPathNode(IXamlIlLineInfo lineInfo, IXamlIlType compiledBindingType, IList<BindingExpressionGrammar.INode> path)
public ParsedBindingPathNode(IXamlLineInfo lineInfo, IXamlType compiledBindingType, IList<BindingExpressionGrammar.INode> path)
: base(lineInfo)
{
Type = new XamlIlAstClrTypeReference(lineInfo, compiledBindingType, false);
Type = new XamlAstClrTypeReference(lineInfo, compiledBindingType, false);
Path = path;
}
public IXamlIlAstTypeReference Type { get; }
public IXamlAstTypeReference Type { get; }
public IList<BindingExpressionGrammar.INode> Path { get; }
public override void VisitChildren(IXamlIlAstVisitor visitor)
public override void VisitChildren(IXamlAstVisitor visitor)
{
for (int i = 0; i < Path.Count; i++)
{
if (Path[i] is IXamlIlAstNode ast)
if (Path[i] is IXamlAstNode ast)
{
Path[i] = (BindingExpressionGrammar.INode)ast.Visit(visitor);
}
@ -297,34 +299,34 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
class VisualAncestorBindingExpressionNode : BindingExpressionGrammar.INode
{
public IXamlIlType Type { get; set; }
public IXamlType Type { get; set; }
public int Level { get; set; }
}
class LogicalAncestorBindingExpressionNode : BindingExpressionGrammar.INode
{
public IXamlIlType Type { get; set; }
public IXamlType Type { get; set; }
public int Level { get; set; }
}
class TemplatedParentBindingExpressionNode : BindingExpressionGrammar.INode
{
public IXamlIlType Type { get; set; }
public IXamlType Type { get; set; }
}
class RawSourceBindingExpressionNode : XamlIlAstNode, BindingExpressionGrammar.INode
class RawSourceBindingExpressionNode : XamlAstNode, BindingExpressionGrammar.INode
{
public RawSourceBindingExpressionNode(IXamlIlAstValueNode rawSource)
public RawSourceBindingExpressionNode(IXamlAstValueNode rawSource)
: base(rawSource)
{
RawSource = rawSource;
}
public IXamlIlAstValueNode RawSource { get; private set; }
public IXamlAstValueNode RawSource { get; private set; }
public override void VisitChildren(IXamlIlAstVisitor visitor)
public override void VisitChildren(IXamlAstVisitor visitor)
{
RawSource = (IXamlIlAstValueNode)RawSource.Visit(visitor);
RawSource = (IXamlAstValueNode)RawSource.Visit(visitor);
}
}
}

18
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@ -2,24 +2,24 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlBindingPathTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlBindingPathTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
if (node is XamlAstConstructableObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
{
IXamlIlType startType;
IXamlType startType;
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentDataContextNode is null)
{
throw new XamlIlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
}
startType = parentDataContextNode.DataContextType;

8
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs

@ -1,12 +1,12 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlCompiledBindingsMetadataRemover : IXamlIlAstTransformer
class AvaloniaXamlIlCompiledBindingsMetadataRemover : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
while (true)
{

28
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs

@ -1,15 +1,17 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlConstructorServiceProviderTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlConstructorServiceProviderTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode on && on.Arguments.Count == 0)
if (node is XamlAstObjectNode on && on.Arguments.Count == 0)
{
var ctors = on.Type.GetClrType().Constructors;
if (!ctors.Any(c => c.IsPublic && !c.IsStatic && c.Parameters.Count == 0))
@ -27,20 +29,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
class InjectServiceProviderNode : XamlIlAstNode, IXamlIlAstValueNode,IXamlIlAstNodeNeedsParentStack,
IXamlIlAstEmitableNode
class InjectServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
public InjectServiceProviderNode(IXamlIlType type, IXamlIlLineInfo lineInfo) : base(lineInfo)
public InjectServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo)
{
Type = new XamlIlAstClrTypeReference(lineInfo, type, false);
Type = new XamlAstClrTypeReference(lineInfo, type, false);
}
public IXamlIlAstTypeReference Type { get; }
public IXamlAstTypeReference Type { get; }
public bool NeedsParentStack => true;
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldloc(context.ContextLocal);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
}
}

28
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs

@ -1,29 +1,29 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlIlAstObjectNode on
if (!(node is XamlAstObjectNode on
&& on.Type.GetClrType().FullName == "Avalonia.Markup.Xaml.Templates.ControlTemplate"))
return node;
var tt = on.Children.OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(ch =>
var tt = on.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(ch =>
ch.Property.GetClrProperty().Name == "TargetType");
if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode)
// Deja vu. I've just been in this place before
return node;
IXamlIlAstTypeReference targetType;
IXamlAstTypeReference targetType;
var templatableBaseType = context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control");
if ((tt?.Values.FirstOrDefault() is XamlIlTypeExtensionNode tn))
if ((tt?.Values.FirstOrDefault() is XamlTypeExtensionNode tn))
{
targetType = tn.Value;
}
@ -33,11 +33,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.FirstOrDefault();
if (parentScope?.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
targetType = parentScope.TargetType;
else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlIlAstObjectNode directParentNode
else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlAstObjectNode directParentNode
&& templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType()))
targetType = directParentNode.Type;
else
targetType = new XamlIlAstClrTypeReference(node,
targetType = new XamlAstClrTypeReference(node,
templatableBaseType, false);
}
@ -48,9 +48,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
class AvaloniaXamlIlTargetTypeMetadataNode : XamlIlValueWithSideEffectNodeBase
class AvaloniaXamlIlTargetTypeMetadataNode : XamlValueWithSideEffectNodeBase
{
public IXamlIlAstTypeReference TargetType { get; set; }
public IXamlAstTypeReference TargetType { get; set; }
public ScopeTypes ScopeType { get; }
public enum ScopeTypes
@ -60,7 +60,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Transitions
}
public AvaloniaXamlIlTargetTypeMetadataNode(IXamlIlAstValueNode value, IXamlIlAstTypeReference targetType,
public AvaloniaXamlIlTargetTypeMetadataNode(IXamlAstValueNode value, IXamlAstTypeReference targetType,
ScopeTypes type)
: base(value, value)
{

58
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -6,18 +6,18 @@ using System.Text;
using Avalonia.Markup.Parsers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Utilities;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlDataContextTypeTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlDataContextTypeTransformer : IXamlAstTransformer
{
private const string AvaloniaNs = "https://github.com/avaloniaui";
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlDataContextTypeMetadataNode)
{
@ -25,7 +25,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
if (node is XamlIlAstObjectNode on)
if (node is XamlAstConstructableObjectNode on)
{
AvaloniaXamlIlDataContextTypeMetadataNode inferredDataContextTypeNode = null;
AvaloniaXamlIlDataContextTypeMetadataNode directiveDataContextTypeNode = null;
@ -34,7 +34,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
for (int i = 0; i < on.Children.Count; ++i)
{
var child = on.Children[i];
if (child is XamlIlAstXmlDirective directive)
if (child is XamlAstXmlDirective directive)
{
if (directive.Namespace == XamlNamespaces.Xaml2006
&& directive.Name == "DataType"
@ -42,29 +42,29 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
on.Children.RemoveAt(i);
i--;
if (directive.Values[0] is XamlIlAstTextNode text)
if (directive.Values[0] is XamlAstTextNode text)
{
directiveDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on,
XamlIlTypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type);
TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type);
}
else
{
throw new XamlIlParseException("x:DataType should be set to a type name.", directive.Values[0]);
throw new XamlX.XamlParseException("x:DataType should be set to a type name.", directive.Values[0]);
}
}
}
else if (child is XamlIlPropertyAssignmentNode pa)
else if (child is XamlPropertyAssignmentNode pa)
{
if (pa.Property.Name == "DataContext"
&& pa.Property.DeclaringType.Equals(context.GetAvaloniaTypes().StyledElement)
&& pa.Values[0] is XamlIlMarkupExtensionNode ext
&& ext.Value is XamlIlAstObjectNode obj)
&& pa.Values[0] is XamlMarkupExtensionNode ext
&& ext.Value is XamlAstConstructableObjectNode obj)
{
inferredDataContextTypeNode = ParseDataContext(context, on, obj);
}
else if(isDataTemplate
&& pa.Property.Name == "DataType"
&& pa.Values[0] is XamlIlTypeExtensionNode dataTypeNode)
&& pa.Values[0] is XamlTypeExtensionNode dataTypeNode)
{
inferredDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on, dataTypeNode.Value.GetClrType());
}
@ -78,7 +78,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (isDataTemplate && inferredDataContextTypeNode is null)
{
// Infer data type from collection binding on a control that displays items.
var parentObject = context.ParentNodes().OfType<XamlIlAstObjectNode>().FirstOrDefault();
var parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault();
if (parentObject != null && context.GetAvaloniaTypes().IItemsPresenterHost.IsDirectlyAssignableFrom(parentObject.Type.GetClrType()))
{
inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject);
@ -96,10 +96,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
private static AvaloniaXamlIlDataContextTypeMetadataNode InferDataContextOfPresentedItem(XamlIlAstTransformationContext context, XamlIlAstObjectNode on, XamlIlAstObjectNode parentObject)
private static AvaloniaXamlIlDataContextTypeMetadataNode InferDataContextOfPresentedItem(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode parentObject)
{
var parentItemsValue = parentObject
.Children.OfType<XamlIlPropertyAssignmentNode>()
.Children.OfType<XamlPropertyAssignmentNode>()
.FirstOrDefault(pa => pa.Property.Name == "Items")
?.Values[0];
if (parentItemsValue is null)
@ -109,11 +109,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
}
IXamlIlType itemsCollectionType = null;
IXamlType itemsCollectionType = null;
if (context.GetAvaloniaTypes().IBinding.IsAssignableFrom(parentItemsValue.Type.GetClrType()))
{
if (parentItemsValue.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)
&& parentItemsValue is XamlIlMarkupExtensionNode ext && ext.Value is XamlIlAstObjectNode parentItemsBinding)
&& parentItemsValue is XamlMarkupExtensionNode ext && ext.Value is XamlAstConstructableObjectNode parentItemsBinding)
{
var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentItemsDataContext != null)
@ -141,7 +141,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
}
private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(XamlIlAstTransformationContext context, XamlIlAstObjectNode on, XamlIlAstObjectNode obj)
private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode obj)
{
var bindingType = context.GetAvaloniaTypes().IBinding;
if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension))
@ -150,11 +150,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else if (obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
{
IXamlIlType startType;
IXamlType startType;
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentDataContextNode is null)
{
throw new XamlIlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
}
startType = parentDataContextNode.DataContextType;
@ -168,11 +168,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
[DebuggerDisplay("DataType = {DataContextType}")]
class AvaloniaXamlIlDataContextTypeMetadataNode : XamlIlValueWithSideEffectNodeBase
class AvaloniaXamlIlDataContextTypeMetadataNode : XamlValueWithSideEffectNodeBase
{
public virtual IXamlIlType DataContextType { get; }
public virtual IXamlType DataContextType { get; }
public AvaloniaXamlIlDataContextTypeMetadataNode(IXamlIlAstValueNode value, IXamlIlType targetType)
public AvaloniaXamlIlDataContextTypeMetadataNode(IXamlAstValueNode value, IXamlType targetType)
: base(value, value)
{
DataContextType = targetType;
@ -182,11 +182,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
[DebuggerDisplay("DataType = Unknown")]
class AvaloniaXamlIlUninferrableDataContextMetadataNode : AvaloniaXamlIlDataContextTypeMetadataNode
{
public AvaloniaXamlIlUninferrableDataContextMetadataNode(IXamlIlAstValueNode value)
public AvaloniaXamlIlUninferrableDataContextMetadataNode(IXamlAstValueNode value)
: base(value, null)
{
}
public override IXamlIlType DataContextType => throw new XamlIlTransformException("Unable to infer DataContext type for compiled bindings nested within this element.", Value);
public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element.", Value);
}
}

26
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs

@ -1,12 +1,12 @@
using System.Collections.Generic;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlDesignPropertiesTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlDesignPropertiesTransformer : IXamlAstTransformer
{
public bool IsDesignMode { get; set; }
@ -17,14 +17,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
};
private const string AvaloniaNs = "https://github.com/avaloniaui";
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode on)
if (node is XamlAstObjectNode on)
{
for (var c=0; c<on.Children.Count;)
{
var ch = on.Children[c];
if (ch is XamlIlAstXmlDirective directive
if (ch is XamlAstXmlDirective directive
&& directive.Namespace == XamlNamespaces.Blend2008
&& DesignDirectives.TryGetValue(directive.Name, out var mapTo))
{
@ -34,9 +34,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else
{
// Map to an actual property in `Design` class
on.Children[c] = new XamlIlAstXamlPropertyValueNode(ch,
new XamlIlAstNamePropertyReference(ch,
new XamlIlAstXmlTypeReference(ch, AvaloniaNs, "Design"),
on.Children[c] = new XamlAstXamlPropertyValueNode(ch,
new XamlAstNamePropertyReference(ch,
new XamlAstXmlTypeReference(ch, AvaloniaNs, "Design"),
mapTo, on.Type), directive.Values);
c++;
}
@ -44,9 +44,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
// Remove all "Design" attached properties in non-design mode
else if (
!IsDesignMode
&& ch is XamlIlAstXamlPropertyValueNode pv
&& pv.Property is XamlIlAstNamePropertyReference pref
&& pref.DeclaringType is XamlIlAstXmlTypeReference dref
&& ch is XamlAstXamlPropertyValueNode pv
&& pv.Property is XamlAstNamePropertyReference pref
&& pref.DeclaringType is XamlAstXmlTypeReference dref
&& dref.XmlNamespace == AvaloniaNs && dref.Name == "Design"
)
{

8
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs

@ -1,12 +1,12 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlMetadataRemover : IXamlIlAstTransformer
class AvaloniaXamlIlMetadataRemover : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is AvaloniaXamlIlTargetTypeMetadataNode targetType)
return targetType.Value;

96
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs

@ -2,21 +2,23 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Parsers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
using XamlX.Emit;
using XamlX.IL;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlPropertyPathTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlPropertyPathTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstXamlPropertyValueNode pv
if (node is XamlAstXamlPropertyValueNode pv
&& pv.Values.Count == 1
&& pv.Values[0] is XamlIlAstTextNode text
&& pv.Values[0] is XamlAstTextNode text
&& pv.Property.GetClrProperty().Getter?.ReturnType
.Equals(context.GetAvaloniaTypes().PropertyPath) == true
)
@ -24,9 +26,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
.FirstOrDefault();
if(parentScope == null)
throw new XamlIlParseException("No target type scope found for property path", text);
throw new XamlX.XamlParseException("No target type scope found for property path", text);
if (parentScope.ScopeType != AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
throw new XamlIlParseException("PropertyPath is currently only valid for styles", pv);
throw new XamlX.XamlParseException("PropertyPath is currently only valid for styles", pv);
IEnumerable<PropertyPathGrammar.ISyntax> parsed;
@ -36,11 +38,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
catch (Exception e)
{
throw new XamlIlParseException("Unable to parse PropertyPath: " + e.Message, text);
throw new XamlX.XamlParseException("Unable to parse PropertyPath: " + e.Message, text);
}
var elements = new List<IXamlIlPropertyPathElementNode>();
IXamlIlType currentType = parentScope.TargetType.GetClrType();
IXamlType currentType = parentScope.TargetType.GetClrType();
var expectProperty = true;
@ -48,16 +50,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var expectTraversal = false;
var types = context.GetAvaloniaTypes();
IXamlIlType GetType(string ns, string name)
IXamlType GetType(string ns, string name)
{
return XamlIlTypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
return TypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
text, true).GetClrType();
}
void HandleProperty(string name, string typeNamespace, string typeName)
{
if(!expectProperty || currentType == null)
throw new XamlIlParseException("Unexpected property node", text);
throw new XamlX.XamlParseException("Unexpected property node", text);
var propertySearchType =
typeName != null ? GetType(typeNamespace, typeName) : currentType;
@ -78,7 +80,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
if (prop == null)
throw new XamlIlParseException(
throw new XamlX.XamlParseException(
$"Unable to resolve property {name} on type {propertySearchType.GetFqn()}",
text);
@ -93,7 +95,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (ge is PropertyPathGrammar.ChildTraversalSyntax)
{
if (!expectTraversal)
throw new XamlIlParseException("Unexpected child traversal .", text);
throw new XamlX.XamlParseException("Unexpected child traversal .", text);
elements.Add(new XamlIlChildTraversalPropertyPathElementNode());
expectTraversal = expectCast = false;
expectProperty = true;
@ -101,7 +103,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets)
{
if(!expectCast)
throw new XamlIlParseException("Unexpected cast node", text);
throw new XamlX.XamlParseException("Unexpected cast node", text);
currentType = GetType(ets.TypeNamespace, ets.TypeName);
elements.Add(new XamlIlCastPropertyPathElementNode(currentType, true));
expectProperty = false;
@ -110,7 +112,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else if (ge is PropertyPathGrammar.CastTypeSyntax cts)
{
if(!expectCast)
throw new XamlIlParseException("Unexpected cast node", text);
throw new XamlX.XamlParseException("Unexpected cast node", text);
//TODO: Check if cast can be done
currentType = GetType(cts.TypeNamespace, cts.TypeName);
elements.Add(new XamlIlCastPropertyPathElementNode(currentType, false));
@ -126,12 +128,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
HandleProperty(tqps.Name, tqps.TypeNamespace, tqps.TypeName);
}
else
throw new XamlIlParseException("Unexpected node " + ge, text);
throw new XamlX.XamlParseException("Unexpected node " + ge, text);
}
var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types);
if (propertyPathNode.Type == null)
throw new XamlIlParseException("Unexpected end of the property path", text);
throw new XamlX.XamlParseException("Unexpected end of the property path", text);
pv.Values[0] = propertyPathNode;
}
@ -140,49 +142,49 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
interface IXamlIlPropertyPathElementNode
{
void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen);
IXamlIlType Type { get; }
void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen);
IXamlType Type { get; }
}
class XamlIlChildTraversalPropertyPathElementNode : IXamlIlPropertyPathElementNode
{
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
=> codeGen.EmitCall(
context.GetAvaloniaTypes()
.PropertyPathBuilder.FindMethod(m => m.Name == "ChildTraversal"));
public IXamlIlType Type => null;
public IXamlType Type => null;
}
class XamlIlAvaloniaPropertyPropertyPathElementNode : IXamlIlPropertyPathElementNode
{
private readonly IXamlIlField _field;
private readonly IXamlField _field;
public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlIlField field, IXamlIlType propertyType)
public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlField field, IXamlType propertyType)
{
_field = field;
Type = propertyType;
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
=> codeGen
.Ldsfld(_field)
.EmitCall(context.GetAvaloniaTypes()
.PropertyPathBuilder.FindMethod(m => m.Name == "Property"));
public IXamlIlType Type { get; }
public IXamlType Type { get; }
}
class XamlIClrPropertyPathElementNode : IXamlIlPropertyPathElementNode
{
private readonly IXamlIlProperty _property;
private readonly IXamlProperty _property;
public XamlIClrPropertyPathElementNode(IXamlIlProperty property)
public XamlIClrPropertyPathElementNode(IXamlProperty property)
{
_property = property;
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
.Emit(context, codeGen, _property);
@ -191,21 +193,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.PropertyPathBuilder.FindMethod(m => m.Name == "Property"));
}
public IXamlIlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
}
class XamlIlCastPropertyPathElementNode : IXamlIlPropertyPathElementNode
{
private readonly IXamlIlType _type;
private readonly IXamlType _type;
private readonly bool _ensureType;
public XamlIlCastPropertyPathElementNode(IXamlIlType type, bool ensureType)
public XamlIlCastPropertyPathElementNode(IXamlType type, bool ensureType)
{
_type = type;
_ensureType = ensureType;
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen
.Ldtype(_type)
@ -213,39 +215,39 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.PropertyPathBuilder.FindMethod(m => m.Name == (_ensureType ? "EnsureType" : "Cast")));
}
public IXamlIlType Type => _type;
public IXamlType Type => _type;
}
class XamlIlPropertyPathNode : XamlIlAstNode, IXamlIlPropertyPathNode, IXamlIlAstEmitableNode
class XamlIlPropertyPathNode : XamlAstNode, IXamlIlPropertyPathNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
private readonly List<IXamlIlPropertyPathElementNode> _elements;
private readonly AvaloniaXamlIlWellKnownTypes _types;
public XamlIlPropertyPathNode(IXamlIlLineInfo lineInfo,
public XamlIlPropertyPathNode(IXamlLineInfo lineInfo,
List<IXamlIlPropertyPathElementNode> elements,
AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
{
_elements = elements;
_types = types;
Type = new XamlIlAstClrTypeReference(this, types.PropertyPath, false);
Type = new XamlAstClrTypeReference(this, types.PropertyPath, false);
}
public IXamlIlAstTypeReference Type { get; }
public IXamlIlType PropertyType => _elements.LastOrDefault()?.Type;
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public IXamlAstTypeReference Type { get; }
public IXamlType PropertyType => _elements.LastOrDefault()?.Type;
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen
.Newobj(_types.PropertyPathBuilder.FindConstructor());
foreach(var e in _elements)
e.Emit(context, codeGen);
codeGen.EmitCall(_types.PropertyPathBuilder.FindMethod(m => m.Name == "Build"));
return XamlIlNodeEmitResult.Type(0, _types.PropertyPath);
return XamlILNodeEmitResult.Type(0, _types.PropertyPath);
}
}
}
interface IXamlIlPropertyPathNode : IXamlIlAstValueNode
interface IXamlIlPropertyPathNode : IXamlAstValueNode
{
IXamlIlType PropertyType { get; }
IXamlType PropertyType { get; }
}
}

45
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs

@ -1,20 +1,22 @@
using System;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
using XamlX.IL;
using XamlX.Emit;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlRootObjectScope : IXamlIlAstTransformer
class AvaloniaXamlIlRootObjectScope : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!context.ParentNodes().Any()
&& node is XamlIlValueWithManipulationNode mnode)
&& node is XamlValueWithManipulationNode mnode)
{
mnode.Manipulation = new XamlIlManipulationGroupNode(mnode,
mnode.Manipulation = new XamlManipulationGroupNode(mnode,
new[]
{
mnode.Manipulation,
@ -23,41 +25,48 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
return node;
}
class HandleRootObjectScopeNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
class HandleRootObjectScopeNode : XamlAstNode, IXamlAstManipulationNode
{
private readonly AvaloniaXamlIlWellKnownTypes _types;
public HandleRootObjectScopeNode(IXamlIlLineInfo lineInfo,
public HandleRootObjectScopeNode(IXamlLineInfo lineInfo,
AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
{
_types = types;
}
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
}
internal class Emitter : IXamlILAstNodeEmitter
{
public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (!(node is HandleRootObjectScopeNode))
{
return null;
}
var types = context.GetAvaloniaTypes();
var next = codeGen.DefineLabel();
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var local = codeGen.LocalsPool.GetLocal(_types.StyledElement))
using (var local = codeGen.LocalsPool.GetLocal(types.StyledElement))
{
codeGen
.Isinst(_types.StyledElement)
.Isinst(types.StyledElement)
.Dup()
.Stloc(local.Local)
.Brfalse(next)
.Ldloc(local.Local)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField)
.EmitCall(_types.NameScopeSetNameScope, true)
.EmitCall(types.NameScopeSetNameScope, true)
.MarkLabel(next)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField)
.EmitCall(_types.INameScopeComplete, true);
.EmitCall(types.INameScopeComplete, true);
}
return XamlIlNodeEmitResult.Void(1);
return XamlILNodeEmitResult.Void(1);
}
}
}
}

118
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -3,41 +3,45 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Parsers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlSelectorTransformer : IXamlIlAstTransformer
using XamlParseException = XamlX.XamlParseException;
using XamlLoadException = XamlX.XamlLoadException;
class AvaloniaXamlIlSelectorTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlIlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style"))
if (!(node is XamlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style"))
return node;
var pn = on.Children.OfType<XamlIlAstXamlPropertyValueNode>()
var pn = on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (pn == null)
return node;
if (pn.Values.Count != 1)
throw new XamlIlParseException("Selector property should should have exactly one value", node);
throw new XamlParseException("Selector property should should have exactly one value", node);
if (pn.Values[0] is XamlIlSelectorNode)
//Deja vu. I've just been in this place before
return node;
if (!(pn.Values[0] is XamlIlAstTextNode tn))
throw new XamlIlParseException("Selector property should be a text node", node);
if (!(pn.Values[0] is XamlAstTextNode tn))
throw new XamlParseException("Selector property should be a text node", node);
var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
XamlIlSelectorNode Create(IEnumerable<SelectorGrammar.ISyntax> syntax,
Func<string, string, XamlIlAstClrTypeReference> typeResolver)
Func<string, string, XamlAstClrTypeReference> typeResolver)
{
XamlIlSelectorNode result = initialNode;
XamlIlOrSelectorNode results = null;
@ -63,18 +67,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var type = result?.TargetType;
if (type == null)
throw new XamlIlParseException("Property selectors must be applied to a type.", node);
throw new XamlParseException("Property selectors must be applied to a type.", node);
var targetProperty =
type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
if (targetProperty == null)
throw new XamlIlParseException($"Cannot find '{property.Property}' on '{type}", node);
throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node);
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlIlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String),
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String),
targetProperty.PropertyType, out var typedValue))
throw new XamlIlParseException(
throw new XamlParseException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
node);
@ -100,7 +104,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
result = initialNode;
break;
default:
throw new XamlIlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
throw new XamlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
}
}
@ -119,15 +123,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
catch (Exception e)
{
throw new XamlIlParseException("Unable to parse selector: " + e.Message, node);
throw new XamlParseException("Unable to parse selector: " + e.Message, node);
}
var selector = Create(parsed, (p, n)
=> XamlIlTypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
=> TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
pn.Values[0] = selector;
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlIlAstClrTypeReference(selector, selector.TargetType, false),
new XamlAstClrTypeReference(selector, selector.TargetType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
@ -135,32 +139,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
abstract class XamlIlSelectorNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode
abstract class XamlIlSelectorNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
protected XamlIlSelectorNode Previous { get; }
public abstract IXamlIlType TargetType { get; }
public abstract IXamlType TargetType { get; }
public XamlIlSelectorNode(XamlIlSelectorNode previous,
IXamlIlLineInfo info = null,
IXamlIlType selectorType = null) : base(info ?? previous)
IXamlLineInfo info = null,
IXamlType selectorType = null) : base(info ?? previous)
{
Previous = previous;
Type = selectorType == null ? previous.Type : new XamlIlAstClrTypeReference(this, selectorType, false);
Type = selectorType == null ? previous.Type : new XamlAstClrTypeReference(this, selectorType, false);
}
public IXamlIlAstTypeReference Type { get; }
public IXamlAstTypeReference Type { get; }
public virtual XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public virtual XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (Previous != null)
context.Emit(Previous, codeGen, Type.GetClrType());
DoEmit(context, codeGen);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
protected abstract void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen);
protected abstract void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen);
protected void EmitCall(XamlIlEmitContext context, IXamlIlEmitter codeGen, Func<IXamlIlMethod, bool> method)
protected void EmitCall(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen, Func<IXamlMethod, bool> method)
{
var selectors = context.Configuration.TypeSystem.GetType("Avalonia.Styling.Selectors");
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 && method(m));
@ -170,27 +174,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
class XamlIlSelectorInitialNode : XamlIlSelectorNode
{
public XamlIlSelectorInitialNode(IXamlIlLineInfo info,
IXamlIlType selectorType) : base(null, info, selectorType)
public XamlIlSelectorInitialNode(IXamlLineInfo info,
IXamlType selectorType) : base(null, info, selectorType)
{
}
public override IXamlIlType TargetType => null;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen) => codeGen.Ldnull();
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) => codeGen.Ldnull();
}
class XamlIlTypeSelector : XamlIlSelectorNode
{
public bool Concrete { get; }
public XamlIlTypeSelector(XamlIlSelectorNode previous, IXamlIlType type, bool concrete) : base(previous)
public XamlIlTypeSelector(XamlIlSelectorNode previous, IXamlType type, bool concrete) : base(previous)
{
TargetType = type;
Concrete = concrete;
}
public override IXamlIlType TargetType { get; }
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType { get; }
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var name = Concrete ? "OfType" : "Is";
codeGen.Ldtype(TargetType);
@ -217,8 +221,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
public override IXamlIlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldstr(String);
var name = _type.ToString();
@ -242,8 +246,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
_type = type;
}
public override IXamlIlType TargetType => null;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var name = _type.ToString();
EmitCall(context, codeGen,
@ -260,8 +264,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Argument = argument;
}
public override IXamlIlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
context.Emit(Argument, codeGen, Type.GetClrType());
EmitCall(context, codeGen,
@ -272,22 +276,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
class XamlIlPropertyEqualsSelector : XamlIlSelectorNode
{
public XamlIlPropertyEqualsSelector(XamlIlSelectorNode previous,
IXamlIlProperty property,
IXamlIlAstValueNode value)
IXamlProperty property,
IXamlAstValueNode value)
: base(previous)
{
Property = property;
Value = value;
}
public IXamlIlProperty Property { get; set; }
public IXamlIlAstValueNode Value { get; set; }
public IXamlProperty Property { get; set; }
public IXamlAstValueNode Value { get; set; }
public override IXamlIlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
throw new XamlIlLoadException(
throw new XamlLoadException(
$"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty",
this);
context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
@ -302,7 +306,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
class XamlIlOrSelectorNode : XamlIlSelectorNode
{
List<XamlIlSelectorNode> _selectors = new List<XamlIlSelectorNode>();
public XamlIlOrSelectorNode(IXamlIlLineInfo info, IXamlIlType selectorType) : base(null, info, selectorType)
public XamlIlOrSelectorNode(IXamlLineInfo info, IXamlType selectorType) : base(null, info, selectorType)
{
}
@ -311,11 +315,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
_selectors.Add(node);
}
public override IXamlIlType TargetType
public override IXamlType TargetType
{
get
{
IXamlIlType result = null;
IXamlType result = null;
foreach (var selector in _selectors)
{
@ -340,10 +344,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (_selectors.Count == 0)
throw new XamlIlLoadException("Invalid selector count", this);
throw new XamlLoadException("Invalid selector count", this);
if (_selectors.Count == 1)
{
_selectors[0].Emit(context, codeGen);

74
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@ -2,74 +2,78 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data.Core;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlSetterTransformer : IXamlIlAstTransformer
using XamlParseException = XamlX.XamlParseException;
using XamlLoadException = XamlX.XamlLoadException;
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlIlAstObjectNode on
if (!(node is XamlAstObjectNode on
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node;
var parent = context.ParentNodes().OfType<XamlIlAstObjectNode>()
var parent = context.ParentNodes().OfType<XamlAstObjectNode>()
.FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style");
if (parent == null)
throw new XamlIlParseException(
throw new XamlParseException(
"Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node);
var selectorProperty = parent.Children.OfType<XamlIlAstXamlPropertyValueNode>()
var selectorProperty = parent.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (selectorProperty == null)
throw new XamlIlParseException(
throw new XamlParseException(
"Can not find parent Style Selector", node);
var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode;
if (selector?.TargetType == null)
throw new XamlIlParseException(
throw new XamlParseException(
"Can not resolve parent Style Selector type", node);
IXamlIlType propType = null;
var property = @on.Children.OfType<XamlIlAstXamlPropertyValueNode>()
IXamlType propType = null;
var property = @on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property");
if (property != null)
{
var propertyName = property.Values.OfType<XamlIlAstTextNode>().FirstOrDefault()?.Text;
var propertyName = property.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text;
if (propertyName == null)
throw new XamlIlParseException("Setter.Property must be a string", node);
throw new XamlParseException("Setter.Property must be a string", node);
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
property.Values = new List<IXamlIlAstValueNode> {avaloniaPropertyNode};
new XamlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
property.Values = new List<IXamlAstValueNode> {avaloniaPropertyNode};
propType = avaloniaPropertyNode.AvaloniaPropertyType;
}
else
{
var propertyPath = on.Children.OfType<XamlIlAstXamlPropertyValueNode>()
var propertyPath = on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "PropertyPath");
if (propertyPath == null)
throw new XamlIlParseException("Setter without a property or property path is not valid", node);
throw new XamlX.XamlParseException("Setter without a property or property path is not valid", node);
if (propertyPath.Values[0] is IXamlIlPropertyPathNode ppn
&& ppn.PropertyType != null)
propType = ppn.PropertyType;
else
throw new XamlIlParseException("Unable to get the property path property type", node);
throw new XamlX.XamlParseException("Unable to get the property path property type", node);
}
var valueProperty = on.Children
.OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value");
if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode)
.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value");
if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlAstTextNode)
{
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
propType, out var converted))
throw new XamlIlParseException(
throw new XamlParseException(
$"Unable to convert property value to {propType.GetFqn()}",
valueProperty.Values[0]);
@ -80,9 +84,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
class SetterValueProperty : XamlIlAstClrProperty
class SetterValueProperty : XamlAstClrProperty
{
public SetterValueProperty(IXamlIlLineInfo line, IXamlIlType setterType, IXamlIlType targetType,
public SetterValueProperty(IXamlLineInfo line, IXamlType setterType, IXamlType targetType,
AvaloniaXamlIlWellKnownTypes types)
: base(line, "Value", setterType, null)
{
@ -93,21 +97,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType));
}
class XamlIlDirectCallPropertySetter : IXamlIlPropertySetter
class XamlIlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter<IXamlILEmitter>
{
private readonly IXamlIlMethod _method;
private readonly IXamlIlType _type;
public IXamlIlType TargetType { get; }
private readonly IXamlMethod _method;
private readonly IXamlType _type;
public IXamlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
public IReadOnlyList<IXamlIlType> Parameters { get; }
public void Emit(IXamlIlEmitter codegen)
public IReadOnlyList<IXamlType> Parameters { get; }
public void Emit(IXamlILEmitter codegen)
{
if (_type.IsValueType)
codegen.Box(_type);
codegen.EmitCall(_method, true);
}
public XamlIlDirectCallPropertySetter(IXamlIlMethod method, IXamlIlType type)
public XamlIlDirectCallPropertySetter(IXamlMethod method, IXamlType type)
{
_method = method;
_type = type;

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

@ -1,20 +1,22 @@
using System.Collections.Generic;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlTransformInstanceAttachedProperties : IXamlIlAstTransformer
class AvaloniaXamlIlTransformInstanceAttachedProperties : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstNamePropertyReference prop
&& prop.TargetType is XamlIlAstClrTypeReference targetRef
&& prop.DeclaringType is XamlIlAstClrTypeReference declaringRef)
if (node is XamlAstNamePropertyReference prop
&& prop.TargetType is XamlAstClrTypeReference targetRef
&& prop.DeclaringType is XamlAstClrTypeReference declaringRef)
{
// Target and declared type aren't assignable but both inherit from AvaloniaObject
var avaloniaObject = context.Configuration.TypeSystem.FindType("Avalonia.AvaloniaObject");
@ -70,21 +72,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
class AvaloniaAttachedInstanceProperty : XamlIlAstClrProperty, IXamlIlAvaloniaProperty
class AvaloniaAttachedInstanceProperty : XamlAstClrProperty, IXamlIlAvaloniaProperty
{
private readonly XamlIlTransformerConfiguration _config;
private readonly IXamlIlType _declaringType;
private readonly IXamlIlType _avaloniaPropertyType;
private readonly IXamlIlType _avaloniaObject;
private readonly IXamlIlField _field;
public AvaloniaAttachedInstanceProperty(XamlIlAstNamePropertyReference prop,
XamlIlTransformerConfiguration config,
IXamlIlType declaringType,
IXamlIlType type,
IXamlIlType avaloniaPropertyType,
IXamlIlType avaloniaObject,
IXamlIlField field) : base(prop, prop.Name,
private readonly TransformerConfiguration _config;
private readonly IXamlType _declaringType;
private readonly IXamlType _avaloniaPropertyType;
private readonly IXamlType _avaloniaObject;
private readonly IXamlField _field;
public AvaloniaAttachedInstanceProperty(XamlAstNamePropertyReference prop,
TransformerConfiguration config,
IXamlType declaringType,
IXamlType type,
IXamlType avaloniaPropertyType,
IXamlType avaloniaObject,
IXamlField field) : base(prop, prop.Name,
declaringType, null)
@ -104,11 +106,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Getter = new GetterMethod(this);
}
public IXamlIlType PropertyType { get; }
public IXamlType PropertyType { get; }
public IXamlIlField AvaloniaProperty => _field;
public IXamlField AvaloniaProperty => _field;
class SetterMethod : IXamlIlPropertySetter
class SetterMethod : IXamlPropertySetter, IXamlEmitablePropertySetter<IXamlILEmitter>
{
private readonly AvaloniaAttachedInstanceProperty _parent;
@ -118,10 +120,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Parameters = new[] {_parent._avaloniaObject, _parent.PropertyType};
}
public IXamlIlType TargetType => _parent.DeclaringType;
public IXamlType TargetType => _parent.DeclaringType;
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
public IReadOnlyList<IXamlIlType> Parameters { get; }
public void Emit(IXamlIlEmitter emitter)
public IReadOnlyList<IXamlType> Parameters { get; }
public void Emit(IXamlILEmitter emitter)
{
var so = _parent._config.WellKnownTypes.Object;
var method = _parent._avaloniaObject
@ -133,7 +135,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
&& m.Parameters[2].IsEnum
);
if (method == null)
throw new XamlIlTypeSystemException(
throw new XamlTypeSystemException(
"Unable to find SetValue(AvaloniaProperty, object, BindingPriority) on AvaloniaObject");
using (var loc = emitter.LocalsPool.GetLocal(_parent.PropertyType))
emitter
@ -150,7 +152,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
class GetterMethod : IXamlIlCustomEmitMethod
class GetterMethod : IXamlCustomEmitMethod<IXamlILEmitter>
{
public GetterMethod(AvaloniaAttachedInstanceProperty parent)
{
@ -163,16 +165,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public bool IsPublic => true;
public bool IsStatic => true;
public string Name { get; protected set; }
public IXamlIlType DeclaringType { get; }
public IXamlIlMethod MakeGenericMethod(IReadOnlyList<IXamlIlType> typeArguments)
public IXamlType DeclaringType { get; }
public IXamlMethod MakeGenericMethod(IReadOnlyList<IXamlType> typeArguments)
=> throw new System.NotSupportedException();
public bool Equals(IXamlIlMethod other) =>
public bool Equals(IXamlMethod other) =>
other is GetterMethod m && m.Name == Name && m.DeclaringType.Equals(DeclaringType);
public IXamlIlType ReturnType => Parent.PropertyType;
public IReadOnlyList<IXamlIlType> Parameters { get; }
public void EmitCall(IXamlIlEmitter emitter)
public IXamlType ReturnType => Parent.PropertyType;
public IReadOnlyList<IXamlType> Parameters { get; }
public void EmitCall(IXamlILEmitter emitter)
{
var method = Parent._avaloniaObject
.FindMethod(m => m.IsPublic && !m.IsStatic && m.Name == "GetValue"
@ -180,7 +182,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
m.Parameters.Count == 1
&& m.Parameters[0].Equals(Parent._avaloniaPropertyType));
if (method == null)
throw new XamlIlTypeSystemException(
throw new XamlTypeSystemException(
"Unable to find T GetValue<T>(AvaloniaProperty<T>) on AvaloniaObject");
emitter
.Ldsfld(Parent._field)

18
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs

@ -1,18 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlTransformSyntheticCompiledBindingMembers : IXamlIlAstTransformer
class AvaloniaXamlIlTransformSyntheticCompiledBindingMembers : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstNamePropertyReference prop
&& prop.TargetType is XamlIlAstClrTypeReference targetRef
if (node is XamlAstNamePropertyReference prop
&& prop.TargetType is XamlAstClrTypeReference targetRef
&& targetRef.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
{
if (prop.Name == "ElementName")
@ -43,12 +43,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Source
}
class AvaloniaSyntheticCompiledBindingProperty : XamlIlAstNode, IXamlIlAstPropertyReference
class AvaloniaSyntheticCompiledBindingProperty : XamlAstNode, IXamlAstPropertyReference
{
public SyntheticCompiledBindingPropertyName Name { get; }
public AvaloniaSyntheticCompiledBindingProperty(
IXamlIlLineInfo lineInfo,
IXamlLineInfo lineInfo,
SyntheticCompiledBindingPropertyName name)
: base(lineInfo)
{

12
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs

@ -1,17 +1,17 @@
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlTransitionsTypeMetadataTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlTransitionsTypeMetadataTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode on)
if (node is XamlAstObjectNode on)
{
foreach (var ch in on.Children)
{
if (ch is XamlIlAstXamlPropertyValueNode pn
if (ch is XamlAstXamlPropertyValueNode pn
&& pn.Property.GetClrProperty().Getter?.ReturnType.Equals(context.GetAvaloniaTypes().Transitions) == true)
{
for (var c = 0; c < pn.Values.Count; c++)

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

@ -1,47 +1,49 @@
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlWellKnownTypes
{
public IXamlIlType AvaloniaObject { get; }
public IXamlIlType IAvaloniaObject { get; }
public IXamlIlType BindingPriority { get; }
public IXamlIlType AvaloniaObjectExtensions { get; }
public IXamlIlType AvaloniaProperty { get; }
public IXamlIlType AvaloniaPropertyT { get; }
public IXamlIlType IBinding { get; }
public IXamlIlMethod AvaloniaObjectBindMethod { get; }
public IXamlIlMethod AvaloniaObjectSetValueMethod { get; }
public IXamlIlType IDisposable { get; }
public XamlIlTypeWellKnownTypes XamlIlTypes { get; }
public XamlIlLanguageTypeMappings XamlIlMappings { get; }
public IXamlIlType Transitions { get; }
public IXamlIlType AssignBindingAttribute { get; }
public IXamlIlType UnsetValueType { get; }
public IXamlIlType StyledElement { get; }
public IXamlIlType NameScope { get; }
public IXamlIlMethod NameScopeSetNameScope { get; }
public IXamlIlType INameScope { get; }
public IXamlIlMethod INameScopeRegister { get; }
public IXamlIlMethod INameScopeComplete { get; }
public IXamlIlType IPropertyInfo { get; }
public IXamlIlType ClrPropertyInfo { get; }
public IXamlIlType PropertyPath { get; }
public IXamlIlType PropertyPathBuilder { get; }
public IXamlIlType IPropertyAccessor { get; }
public IXamlIlType PropertyInfoAccessorFactory { get; }
public IXamlIlType CompiledBindingPathBuilder { get; }
public IXamlIlType CompiledBindingPath { get; }
public IXamlIlType CompiledBindingExtension { get; }
public IXamlIlType DataTemplate { get; }
public IXamlIlType IItemsPresenterHost { get; }
public IXamlIlType ReflectionBindingExtension { get; }
public IXamlType AvaloniaObject { get; }
public IXamlType IAvaloniaObject { get; }
public IXamlType BindingPriority { get; }
public IXamlType AvaloniaObjectExtensions { get; }
public IXamlType AvaloniaProperty { get; }
public IXamlType AvaloniaPropertyT { get; }
public IXamlType IBinding { get; }
public IXamlMethod AvaloniaObjectBindMethod { get; }
public IXamlMethod AvaloniaObjectSetValueMethod { get; }
public IXamlType IDisposable { get; }
public XamlTypeWellKnownTypes XamlIlTypes { get; }
public XamlLanguageTypeMappings XamlIlMappings { get; }
public IXamlType Transitions { get; }
public IXamlType AssignBindingAttribute { get; }
public IXamlType UnsetValueType { get; }
public IXamlType StyledElement { get; }
public IXamlType NameScope { get; }
public IXamlMethod NameScopeSetNameScope { get; }
public IXamlType INameScope { get; }
public IXamlMethod INameScopeRegister { get; }
public IXamlMethod INameScopeComplete { get; }
public IXamlType IPropertyInfo { get; }
public IXamlType ClrPropertyInfo { get; }
public IXamlType PropertyPath { get; }
public IXamlType PropertyPathBuilder { get; }
public IXamlType IPropertyAccessor { get; }
public IXamlType PropertyInfoAccessorFactory { get; }
public IXamlType CompiledBindingPathBuilder { get; }
public IXamlType CompiledBindingPath { get; }
public IXamlType CompiledBindingExtension { get; }
public IXamlType DataTemplate { get; }
public IXamlType IItemsPresenterHost { get; }
public IXamlType ReflectionBindingExtension { get; }
public IXamlIlType RelativeSource { get; }
public IXamlType RelativeSource { get; }
public AvaloniaXamlIlWellKnownTypes(XamlIlTransformerConfiguration cfg)
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
XamlIlTypes = cfg.WellKnownTypes;
AvaloniaObject = cfg.TypeSystem.GetType("Avalonia.AvaloniaObject");
@ -99,7 +101,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
static class AvaloniaXamlIlWellKnownTypesExtensions
{
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlIlAstTransformationContext ctx)
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this AstTransformationContext ctx)
{
if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))
return rv;
@ -107,7 +109,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return rv;
}
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlIlEmitContext ctx)
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> ctx)
{
if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))
return rv;

14
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs

@ -1,17 +1,17 @@
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class IgnoredDirectivesTransformer : IXamlIlAstTransformer
class IgnoredDirectivesTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode no)
if (node is XamlAstObjectNode no)
{
foreach (var d in no.Children.OfType<XamlIlAstXmlDirective>().ToList())
foreach (var d in no.Children.OfType<XamlAstXmlDirective>().ToList())
{
if (d.Namespace == XamlNamespaces.Xaml2006)
{

19
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs

@ -1,30 +1,29 @@
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class XNameTransformer : IXamlIlAstTransformer
class XNameTransformer : IXamlAstTransformer
{
/// <summary>
/// Converts x:Name directives to regular Name assignments
/// </summary>
/// <returns></returns>
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode on)
if (node is XamlAstObjectNode on)
{
for (var c =0; c< on.Children.Count;c++)
{
var ch = on.Children[c];
if (ch is XamlIlAstXmlDirective d
if (ch is XamlAstXmlDirective d
&& d.Namespace == XamlNamespaces.Xaml2006
&& d.Name == "Name")
on.Children[c] = new XamlIlAstXamlPropertyValueNode(d,
new XamlIlAstNamePropertyReference(d, on.Type, "Name", on.Type),
on.Children[c] = new XamlAstXamlPropertyValueNode(d,
new XamlAstNamePropertyReference(d, on.Type, "Name", on.Type),
d.Values);
}
}

111
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs

@ -5,18 +5,23 @@ using System.Xml;
using Avalonia.Markup.Xaml.Parsers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Utilities;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
using XamlX.Emit;
using XamlX.IL;
using XamlIlEmitContext = XamlX.Emit.XamlEmitContext<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
using IXamlIlAstEmitableNode = XamlX.Emit.IXamlAstEmitableNode<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class XamlIlAvaloniaPropertyHelper
{
public static bool EmitProvideValueTarget(XamlIlEmitContext context, IXamlIlEmitter emitter,
XamlIlAstClrProperty property)
public static bool EmitProvideValueTarget(XamlIlEmitContext context, IXamlILEmitter emitter,
XamlAstClrProperty property)
{
if (Emit(context, emitter, property))
return true;
@ -29,7 +34,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return true;
}
public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, XamlIlAstClrProperty property)
public static bool Emit(XamlIlEmitContext context, IXamlILEmitter emitter, XamlAstClrProperty property)
{
if (property is IXamlIlAvaloniaProperty ap)
{
@ -46,7 +51,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return true;
}
public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, IXamlIlProperty property)
public static bool Emit(XamlIlEmitContext context, IXamlILEmitter emitter, IXamlProperty property)
{
var type = (property.Getter ?? property.Setter).DeclaringType;
var name = property.Name + "Property";
@ -58,16 +63,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return true;
}
public static IXamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context,
string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo)
public static IXamlIlAvaloniaPropertyNode CreateNode(AstTransformationContext context,
string propertyName, IXamlAstTypeReference selectorTypeReference, IXamlLineInfo lineInfo)
{
XamlIlAstNamePropertyReference forgedReference;
XamlAstNamePropertyReference forgedReference;
var parser = new PropertyParser();
var parsedPropertyName = parser.Parse(new CharacterReader(propertyName.AsSpan()));
if(parsedPropertyName.owner == null)
forgedReference = new XamlIlAstNamePropertyReference(lineInfo, selectorTypeReference,
forgedReference = new XamlAstNamePropertyReference(lineInfo, selectorTypeReference,
propertyName, selectorTypeReference);
else
{
@ -76,27 +81,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
xmlOwner += ":";
xmlOwner += parsedPropertyName.owner;
var tref = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true);
var tref = TypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true);
var propertyFieldName = parsedPropertyName.name + "Property";
var found = tref.Type.GetAllFields()
.FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName);
if (found == null)
throw new XamlIlParseException(
throw new XamlX.XamlParseException(
$"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo);
return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
}
var clrProperty =
((XamlIlAstClrProperty)new XamlIlPropertyReferenceResolver().Transform(context,
((XamlAstClrProperty)new PropertyReferenceResolver().Transform(context,
forgedReference));
return new XamlIlAvaloniaPropertyNode(lineInfo,
context.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"),
clrProperty);
}
public static IXamlIlType GetAvaloniaPropertyType(IXamlIlField field,
AvaloniaXamlIlWellKnownTypes types, IXamlIlLineInfo lineInfo)
public static IXamlType GetAvaloniaPropertyType(IXamlField field,
AvaloniaXamlIlWellKnownTypes types, IXamlLineInfo lineInfo)
{
var avaloniaPropertyType = field.FieldType;
while (avaloniaPropertyType != null)
@ -109,47 +114,47 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
avaloniaPropertyType = avaloniaPropertyType.BaseType;
}
throw new XamlIlParseException(
throw new XamlX.XamlParseException(
$"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty<T>, make sure to use typed properties",
lineInfo);
}
}
interface IXamlIlAvaloniaPropertyNode : IXamlIlAstValueNode
interface IXamlIlAvaloniaPropertyNode : IXamlAstValueNode
{
IXamlIlType AvaloniaPropertyType { get; }
IXamlType AvaloniaPropertyType { get; }
}
class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
class XamlIlAvaloniaPropertyNode : XamlAstNode, IXamlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
{
public XamlIlAvaloniaPropertyNode(IXamlIlLineInfo lineInfo, IXamlIlType type, XamlIlAstClrProperty property) : base(lineInfo)
public XamlIlAvaloniaPropertyNode(IXamlLineInfo lineInfo, IXamlType type, XamlAstClrProperty property) : base(lineInfo)
{
Type = new XamlIlAstClrTypeReference(this, type, false);
Type = new XamlAstClrTypeReference(this, type, false);
Property = property;
AvaloniaPropertyType = Property.Getter?.ReturnType
?? Property.Setters.First().Parameters[0];
}
public XamlIlAstClrProperty Property { get; }
public XamlAstClrProperty Property { get; }
public IXamlIlAstTypeReference Type { get; }
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public IXamlAstTypeReference Type { get; }
public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
throw new XamlX.XamlLoadException(Property.Name + " is not an AvaloniaProperty", this);
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
public IXamlIlType AvaloniaPropertyType { get; }
public IXamlType AvaloniaPropertyType { get; }
}
class XamlIlAvaloniaPropertyFieldNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
class XamlIlAvaloniaPropertyFieldNode : XamlAstNode, IXamlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
{
private readonly IXamlIlField _field;
private readonly IXamlField _field;
public XamlIlAvaloniaPropertyFieldNode(AvaloniaXamlIlWellKnownTypes types,
IXamlIlLineInfo lineInfo, IXamlIlField field) : base(lineInfo)
IXamlLineInfo lineInfo, IXamlField field) : base(lineInfo)
{
_field = field;
AvaloniaPropertyType = XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(field,
@ -158,25 +163,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public IXamlIlAstTypeReference Type => new XamlIlAstClrTypeReference(this, _field.FieldType, false);
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, _field.FieldType, false);
public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.Ldsfld(_field);
return XamlIlNodeEmitResult.Type(0, _field.FieldType);
return XamlILNodeEmitResult.Type(0, _field.FieldType);
}
public IXamlIlType AvaloniaPropertyType { get; }
public IXamlType AvaloniaPropertyType { get; }
}
interface IXamlIlAvaloniaProperty
{
IXamlIlField AvaloniaProperty { get; }
IXamlField AvaloniaProperty { get; }
}
class XamlIlAvaloniaProperty : XamlIlAstClrProperty, IXamlIlAvaloniaProperty
class XamlIlAvaloniaProperty : XamlAstClrProperty, IXamlIlAvaloniaProperty
{
public IXamlIlField AvaloniaProperty { get; }
public XamlIlAvaloniaProperty(XamlIlAstClrProperty original, IXamlIlField field,
public IXamlField AvaloniaProperty { get; }
public XamlIlAvaloniaProperty(XamlAstClrProperty original, IXamlField field,
AvaloniaXamlIlWellKnownTypes types)
:base(original, original.Name, original.DeclaringType, original.Getter, original.Setters)
{
@ -188,41 +193,41 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Setters.Insert(0, new UnsetValueSetter(types, original.DeclaringType, field));
}
abstract class AvaloniaPropertyCustomSetter : IXamlIlPropertySetter
abstract class AvaloniaPropertyCustomSetter : IXamlPropertySetter, IXamlEmitablePropertySetter<IXamlILEmitter>
{
protected AvaloniaXamlIlWellKnownTypes Types;
protected IXamlIlField AvaloniaProperty;
protected IXamlField AvaloniaProperty;
public AvaloniaPropertyCustomSetter(AvaloniaXamlIlWellKnownTypes types,
IXamlIlType declaringType,
IXamlIlField avaloniaProperty)
IXamlType declaringType,
IXamlField avaloniaProperty)
{
Types = types;
AvaloniaProperty = avaloniaProperty;
TargetType = declaringType;
}
public IXamlIlType TargetType { get; }
public IXamlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters
{
AllowXNull = false
};
public IReadOnlyList<IXamlIlType> Parameters { get; set; }
public abstract void Emit(IXamlIlEmitter codegen);
public IReadOnlyList<IXamlType> Parameters { get; set; }
public abstract void Emit(IXamlILEmitter codegen);
}
class BindingSetter : AvaloniaPropertyCustomSetter
{
public BindingSetter(AvaloniaXamlIlWellKnownTypes types,
IXamlIlType declaringType,
IXamlIlField avaloniaProperty) : base(types, declaringType, avaloniaProperty)
IXamlType declaringType,
IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty)
{
Parameters = new[] {types.IBinding};
}
public override void Emit(IXamlIlEmitter emitter)
public override void Emit(IXamlILEmitter emitter)
{
using (var bloc = emitter.LocalsPool.GetLocal(Types.IBinding))
emitter
@ -237,13 +242,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class UnsetValueSetter : AvaloniaPropertyCustomSetter
{
public UnsetValueSetter(AvaloniaXamlIlWellKnownTypes types, IXamlIlType declaringType, IXamlIlField avaloniaProperty)
public UnsetValueSetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, IXamlField avaloniaProperty)
: base(types, declaringType, avaloniaProperty)
{
Parameters = new[] {types.UnsetValueType};
}
public override void Emit(IXamlIlEmitter codegen)
public override void Emit(IXamlILEmitter codegen)
{
var unsetValue = Types.AvaloniaProperty.Fields.First(f => f.Name == "UnsetValue");
codegen

190
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs

@ -5,20 +5,24 @@ using System.Linq;
using System.Reflection.Emit;
using Avalonia.Markup.Parsers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlIl;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
using XamlX;
using XamlX.Emit;
using XamlX.IL;
using Avalonia.Utilities;
using XamlIlEmitContext = XamlX.Emit.XamlEmitContext<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
static class XamlIlBindingPathHelper
{
public static IXamlIlType UpdateCompiledBindingExtension(XamlIlAstTransformationContext context, XamlIlAstObjectNode binding, IXamlIlType startType)
public static IXamlType UpdateCompiledBindingExtension(AstTransformationContext context, XamlAstConstructableObjectNode binding, IXamlType startType)
{
IXamlIlType bindingResultType = null;
IXamlType bindingResultType = null;
if (binding.Arguments.Count > 0 && binding.Arguments[0] is ParsedBindingPathNode bindingPath)
{
var transformed = TransformBindingPath(
@ -32,7 +36,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
else
{
var bindingPathAssignment = binding.Children.OfType<XamlIlPropertyAssignmentNode>()
var bindingPathAssignment = binding.Children.OfType<XamlPropertyAssignmentNode>()
.FirstOrDefault(v => v.Property.Name == "Path");
if (bindingPathAssignment is null)
@ -60,7 +64,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return bindingResultType;
}
private static IXamlIlBindingPathNode TransformBindingPath(XamlIlAstTransformationContext context, IXamlIlLineInfo lineInfo, IXamlIlType startType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
private static IXamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, IXamlType startType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
{
List<IXamlIlBindingPathElementNode> transformNodes = new List<IXamlIlBindingPathElementNode>();
List<IXamlIlBindingPathElementNode> nodes = new List<IXamlIlBindingPathElementNode>();
@ -75,7 +79,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
transformNodes.Add(new XamlIlNotPathElementNode(context.Configuration.WellKnownTypes.Boolean));
break;
case BindingExpressionGrammar.StreamNode _:
IXamlIlType observableType;
IXamlType observableType;
if (targetType.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) == true)
{
observableType = targetType;
@ -104,7 +108,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
break;
}
throw new XamlIlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
case BindingExpressionGrammar.PropertyNameNode propName:
var avaloniaPropertyFieldNameMaybe = propName.PropertyName + "Property";
var avaloniaPropertyFieldMaybe = targetType.GetAllFields().FirstOrDefault(f =>
@ -121,7 +125,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (clrProperty is null)
{
throw new XamlIlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
throw new XamlX.XamlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
}
nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty));
}
@ -134,7 +138,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
break;
}
IXamlIlProperty property = null;
IXamlProperty property = null;
for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
{
var defaultMemberAttribute = currentType.CustomAttributes.FirstOrDefault(x => x.Type.Namespace == "System.Reflection" && x.Type.Name == "DefaultMemberAttribute");
@ -146,19 +150,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
};
if (property is null)
{
throw new XamlIlParseException($"The type '${targetType}' does not have an indexer.", lineInfo);
throw new XamlX.XamlParseException($"The type '${targetType}' does not have an indexer.", lineInfo);
}
IEnumerable<IXamlIlType> parameters = property.IndexerParameters;
IEnumerable<IXamlType> parameters = property.IndexerParameters;
List<IXamlIlAstValueNode> values = new List<IXamlIlAstValueNode>();
List<IXamlAstValueNode> values = new List<IXamlAstValueNode>();
int currentParamIndex = 0;
foreach (var param in parameters)
{
var textNode = new XamlIlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
var textNode = new XamlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
param, out var converted))
throw new XamlIlParseException(
throw new XamlX.XamlParseException(
$"Unable to convert indexer parameter value of '{indexer.Arguments[currentParamIndex]}' to {param.GetFqn()}",
textNode);
@ -197,14 +201,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var styledElementType = context.GetAvaloniaTypes().StyledElement;
var ancestorType = context
.ParentNodes()
.OfType<XamlIlAstObjectNode>()
.OfType<XamlAstConstructableObjectNode>()
.Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType()))
.ElementAtOrDefault(ancestor.Level)
?.Type.GetClrType();
if (ancestorType is null)
{
throw new XamlIlParseException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
}
nodes.Add(new FindAncestorPathElementNode(ancestorType, ancestor.Level));
@ -215,7 +219,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
break;
case BindingExpressionGrammar.NameNode elementName:
IXamlIlType elementType = null;
IXamlType elementType = null;
foreach (var deferredContent in context.ParentNodes().OfType<NestedScopeMetadataNode>())
{
elementType = ScopeRegistrationFinder.GetTargetType(deferredContent, elementName.Name);
@ -231,7 +235,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (elementType is null)
{
throw new XamlIlParseException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
throw new XamlX.XamlParseException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
}
nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType));
break;
@ -243,17 +247,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return new XamlIlBindingPathNode(lineInfo, context.GetAvaloniaTypes().CompiledBindingPath, transformNodes, nodes);
IXamlIlType GetType(string ns, string name)
IXamlType GetType(string ns, string name)
{
return XamlIlTypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
return TypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
lineInfo, true).GetClrType();
}
}
class ScopeRegistrationFinder : IXamlIlAstVisitor
class ScopeRegistrationFinder : IXamlAstVisitor
{
private Stack<IXamlIlAstNode> _stack = new Stack<IXamlIlAstNode>();
private Stack<IXamlIlAstNode> _childScopesStack = new Stack<IXamlIlAstNode>();
private Stack<IXamlAstNode> _stack = new Stack<IXamlAstNode>();
private Stack<IXamlAstNode> _childScopesStack = new Stack<IXamlAstNode>();
private ScopeRegistrationFinder(string name)
{
@ -262,16 +266,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
string Name { get; }
IXamlIlType TargetType { get; set; }
IXamlType TargetType { get; set; }
public static IXamlIlType GetTargetType(IXamlIlAstNode namescopeRoot, string name)
public static IXamlType GetTargetType(IXamlAstNode namescopeRoot, string name)
{
var finder = new ScopeRegistrationFinder(name);
namescopeRoot.Visit(finder);
return finder.TargetType;
}
void IXamlIlAstVisitor.Pop()
void IXamlAstVisitor.Pop()
{
var node = _stack.Pop();
if (_childScopesStack.Count > 0 && node == _childScopesStack.Peek())
@ -280,7 +284,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
void IXamlIlAstVisitor.Push(IXamlIlAstNode node)
void IXamlAstVisitor.Push(IXamlAstNode node)
{
_stack.Push(node);
if (node is NestedScopeMetadataNode)
@ -289,11 +293,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
IXamlIlAstNode IXamlIlAstVisitor.Visit(IXamlIlAstNode node)
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
{
if (_childScopesStack.Count == 0 && node is AvaloniaNameScopeRegistrationXamlIlNode registration)
{
if (registration.Name is XamlIlAstTextNode text && text.Text == Name)
if (registration.Name is XamlAstTextNode text && text.Text == Name)
{
TargetType = registration.TargetType;
}
@ -304,21 +308,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
interface IXamlIlBindingPathElementNode
{
IXamlIlType Type { get; }
IXamlType Type { get; }
void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen);
void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen);
}
class XamlIlNotPathElementNode : IXamlIlBindingPathElementNode
{
public XamlIlNotPathElementNode(IXamlIlType boolType)
public XamlIlNotPathElementNode(IXamlType boolType)
{
Type = boolType;
}
public IXamlIlType Type { get; }
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Not"));
}
@ -326,14 +330,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class XamlIlStreamObservablePathElementNode : IXamlIlBindingPathElementNode
{
public XamlIlStreamObservablePathElementNode(IXamlIlType type)
public XamlIlStreamObservablePathElementNode(IXamlType type)
{
Type = type;
}
public IXamlIlType Type { get; }
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "StreamObservable").MakeGenericMethod(new[] { Type }));
}
@ -341,14 +345,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class XamlIlStreamTaskPathElementNode : IXamlIlBindingPathElementNode
{
public XamlIlStreamTaskPathElementNode(IXamlIlType type)
public XamlIlStreamTaskPathElementNode(IXamlType type)
{
Type = type;
}
public IXamlIlType Type { get; }
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "StreamTask").MakeGenericMethod(new[] { Type }));
}
@ -356,14 +360,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class SelfPathElementNode : IXamlIlBindingPathElementNode
{
public SelfPathElementNode(IXamlIlType type)
public SelfPathElementNode(IXamlType type)
{
Type = type;
}
public IXamlIlType Type { get; }
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Self"));
}
@ -373,15 +377,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
private readonly int _level;
public FindAncestorPathElementNode(IXamlIlType ancestorType, int level)
public FindAncestorPathElementNode(IXamlType ancestorType, int level)
{
Type = ancestorType;
_level = level;
}
public IXamlIlType Type { get; }
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.Ldtype(Type)
.Ldc_I4(_level)
@ -393,15 +397,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
private readonly int _level;
public FindVisualAncestorPathElementNode(IXamlIlType ancestorType, int level)
public FindVisualAncestorPathElementNode(IXamlType ancestorType, int level)
{
Type = ancestorType;
_level = level;
}
public IXamlIlType Type { get; }
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.Ldtype(Type)
.Ldc_I4(_level)
@ -413,15 +417,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
private readonly string _name;
public ElementNamePathElementNode(string name, IXamlIlType elementType)
public ElementNamePathElementNode(string name, IXamlType elementType)
{
_name = name;
Type = elementType;
}
public IXamlIlType Type { get; }
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
@ -436,15 +440,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class XamlIlAvaloniaPropertyPropertyPathElementNode : IXamlIlBindingPathElementNode
{
private readonly IXamlIlField _field;
private readonly IXamlField _field;
public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlIlField field, IXamlIlType propertyType)
public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlField field, IXamlType propertyType)
{
_field = field;
Type = propertyType;
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.Ldsfld(_field);
context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
@ -453,19 +457,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
}
public IXamlIlType Type { get; }
public IXamlType Type { get; }
}
class XamlIlClrPropertyPathElementNode : IXamlIlBindingPathElementNode
{
private readonly IXamlIlProperty _property;
private readonly IXamlProperty _property;
public XamlIlClrPropertyPathElementNode(IXamlIlProperty property)
public XamlIlClrPropertyPathElementNode(IXamlProperty property)
{
_property = property;
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
.Emit(context, codeGen, _property);
@ -478,17 +482,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
}
public IXamlIlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
}
class XamlIlClrIndexerPathElementNode : IXamlIlBindingPathElementNode
{
private readonly IXamlIlProperty _property;
private readonly List<IXamlIlAstValueNode> _values;
private readonly IXamlProperty _property;
private readonly List<IXamlAstValueNode> _values;
private readonly string _indexerKey;
private readonly bool _isNotifyingCollection;
public XamlIlClrIndexerPathElementNode(IXamlIlProperty property, List<IXamlIlAstValueNode> values, string indexerKey, bool isNotifyingCollection)
public XamlIlClrIndexerPathElementNode(IXamlProperty property, List<IXamlAstValueNode> values, string indexerKey, bool isNotifyingCollection)
{
_property = property;
_values = values;
@ -496,7 +500,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
_isNotifyingCollection = isNotifyingCollection;
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
var intType = context.Configuration.TypeSystem.GetType("System.Int32");
context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
@ -520,15 +524,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
}
public IXamlIlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
}
class XamlIlArrayIndexerPathElementNode : IXamlIlBindingPathElementNode
{
private readonly IXamlIlType _arrayType;
private readonly IXamlType _arrayType;
private readonly List<int> _values;
public XamlIlArrayIndexerPathElementNode(IXamlIlType arrayType, IList<string> values, IXamlIlLineInfo lineInfo)
public XamlIlArrayIndexerPathElementNode(IXamlType arrayType, IList<string> values, IXamlLineInfo lineInfo)
{
_arrayType = arrayType;
_values = new List<int>(values.Count);
@ -536,13 +540,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
if (!int.TryParse(item, out var index))
{
throw new XamlIlParseException($"Unable to convert '{item}' to an integer.", lineInfo.Line, lineInfo.Position);
throw new XamlX.XamlParseException($"Unable to convert '{item}' to an integer.", lineInfo.Line, lineInfo.Position);
}
_values.Add(index);
}
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
var intType = context.Configuration.TypeSystem.GetType("System.Int32");
var indices = codeGen.DefineLocal(intType.MakeArrayType(1));
@ -563,23 +567,23 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "ArrayElement"));
}
public IXamlIlType Type => _arrayType.ArrayElementType;
public IXamlType Type => _arrayType.ArrayElementType;
}
class RawSourcePathElementNode : XamlIlAstNode, IXamlIlBindingPathElementNode
class RawSourcePathElementNode : XamlAstNode, IXamlIlBindingPathElementNode
{
private readonly IXamlIlAstValueNode _rawSource;
private readonly IXamlAstValueNode _rawSource;
public RawSourcePathElementNode(IXamlIlAstValueNode rawSource)
public RawSourcePathElementNode(IXamlAstValueNode rawSource)
:base(rawSource)
{
_rawSource = rawSource;
}
public IXamlIlType Type => _rawSource.Type.GetClrType();
public IXamlType Type => _rawSource.Type.GetClrType();
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
context.Emit(_rawSource, codeGen, Type);
codeGen
@ -588,29 +592,29 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
class XamlIlBindingPathNode : XamlIlAstNode, IXamlIlBindingPathNode, IXamlIlAstEmitableNode
class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
private readonly List<IXamlIlBindingPathElementNode> _transformElements;
private readonly List<IXamlIlBindingPathElementNode> _elements;
public XamlIlBindingPathNode(IXamlIlLineInfo lineInfo,
IXamlIlType bindingPathType,
public XamlIlBindingPathNode(IXamlLineInfo lineInfo,
IXamlType bindingPathType,
List<IXamlIlBindingPathElementNode> transformElements,
List<IXamlIlBindingPathElementNode> elements) : base(lineInfo)
{
Type = new XamlIlAstClrTypeReference(lineInfo, bindingPathType, false);
Type = new XamlAstClrTypeReference(lineInfo, bindingPathType, false);
_transformElements = transformElements;
_elements = elements;
}
public IXamlIlType BindingResultType
public IXamlType BindingResultType
=> _transformElements.Count > 0
? _transformElements[0].Type
: _elements[_elements.Count - 1].Type;
public IXamlIlAstTypeReference Type { get; }
public IXamlAstTypeReference Type { get; }
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
var types = context.GetAvaloniaTypes();
codeGen.Newobj(types.CompiledBindingPathBuilder.FindConstructor());
@ -626,21 +630,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
codeGen.EmitCall(types.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Build"));
return XamlIlNodeEmitResult.Type(0, types.CompiledBindingPath);
return XamlILNodeEmitResult.Type(0, types.CompiledBindingPath);
}
public override void VisitChildren(IXamlIlAstVisitor visitor)
public override void VisitChildren(IXamlAstVisitor visitor)
{
for (int i = 0; i < _transformElements.Count; i++)
{
if (_transformElements[i] is IXamlIlAstNode ast)
if (_transformElements[i] is IXamlAstNode ast)
{
_transformElements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor);
}
}
for (int i = 0; i < _elements.Count; i++)
{
if (_elements[i] is IXamlIlAstNode ast)
if (_elements[i] is IXamlAstNode ast)
{
_elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor);
}
@ -649,8 +653,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
interface IXamlIlBindingPathNode : IXamlIlAstValueNode
interface IXamlIlBindingPathNode : IXamlAstValueNode
{
IXamlIlType BindingResultType { get; }
IXamlType BindingResultType { get; }
}
}

32
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs

@ -2,25 +2,27 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
using XamlX.IL;
using XamlX.Emit;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class XamlIlClrPropertyInfoEmitter
{
private readonly IXamlIlTypeBuilder _builder;
private readonly IXamlTypeBuilder<IXamlILEmitter> _builder;
private Dictionary<string, List<(IXamlIlProperty prop, IXamlIlMethod get)>> _fields
= new Dictionary<string, List<(IXamlIlProperty prop, IXamlIlMethod get)>>();
private Dictionary<string, List<(IXamlProperty prop, IXamlMethod get)>> _fields
= new Dictionary<string, List<(IXamlProperty prop, IXamlMethod get)>>();
public XamlIlClrPropertyInfoEmitter(IXamlIlTypeBuilder builder)
public XamlIlClrPropertyInfoEmitter(IXamlTypeBuilder<IXamlILEmitter> builder)
{
_builder = builder;
}
static string GetKey(IXamlIlProperty property, string indexerArgumentsKey)
static string GetKey(IXamlProperty property, string indexerArgumentsKey)
{
var baseKey = property.Getter.DeclaringType.GetFullName() + "." + property.Name;
@ -32,15 +34,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return baseKey + $"[{indexerArgumentsKey}]";
}
public IXamlIlType Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen, IXamlIlProperty property, IEnumerable<IXamlIlAstValueNode> indexerArguments = null, string indexerArgumentsKey = null)
public IXamlType Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen, IXamlProperty property, IEnumerable<IXamlAstValueNode> indexerArguments = null, string indexerArgumentsKey = null)
{
indexerArguments = indexerArguments ?? Enumerable.Empty<IXamlIlAstValueNode>();
indexerArguments = indexerArguments ?? Enumerable.Empty<IXamlAstValueNode>();
var types = context.GetAvaloniaTypes();
IXamlIlMethod Get()
IXamlMethod Get()
{
var key = GetKey(property, indexerArgumentsKey);
if (!_fields.TryGetValue(key, out var lst))
_fields[key] = lst = new List<(IXamlIlProperty prop, IXamlIlMethod get)>();
_fields[key] = lst = new List<(IXamlProperty prop, IXamlMethod get)>();
foreach (var cached in lst)
{
@ -57,7 +59,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var field = _builder.DefineField(types.IPropertyInfo, name + "!Field", false, true);
void Load(IXamlIlMethod m, IXamlIlEmitter cg)
void Load(IXamlMethod m, IXamlILEmitter cg)
{
cg
.Ldarg_0();
@ -105,7 +107,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
.Ret();
}
var get = _builder.DefineMethod(types.IPropertyInfo, Array.Empty<IXamlIlType>(),
var get = _builder.DefineMethod(types.IPropertyInfo, Array.Empty<IXamlType>(),
name + "!Property", true, true, false);
@ -121,7 +123,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
.MarkLabel(cacheMiss)
.Ldstr(property.Name);
void EmitFunc(IXamlIlEmitter emitter, IXamlIlMethod method, IXamlIlType del)
void EmitFunc(IXamlILEmitter emitter, IXamlMethod method, IXamlType del)
{
if (method == null)
emitter.Ldnull();

30
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs

@ -3,37 +3,41 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
using XamlX.Emit;
using XamlX.IL;
using XamlIlEmitContext = XamlX.Emit.XamlEmitContext<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class XamlIlPropertyInfoAccessorFactoryEmitter
{
private const string IndexerClosureFactoryMethodName = "CreateAccessor";
private readonly IXamlIlTypeBuilder _indexerClosureTypeBuilder;
private IXamlIlType _indexerClosureType;
public XamlIlPropertyInfoAccessorFactoryEmitter(IXamlIlTypeBuilder indexerClosureType)
private readonly IXamlTypeBuilder<IXamlILEmitter> _indexerClosureTypeBuilder;
private IXamlType _indexerClosureType;
public XamlIlPropertyInfoAccessorFactoryEmitter(IXamlTypeBuilder<IXamlILEmitter> indexerClosureType)
{
_indexerClosureTypeBuilder = indexerClosureType;
}
public IXamlIlType EmitLoadInpcPropertyAccessorFactory(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public IXamlType EmitLoadInpcPropertyAccessorFactory(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.Ldnull();
EmitLoadPropertyAccessorFactory(context, codeGen, context.GetAvaloniaTypes().PropertyInfoAccessorFactory, "CreateInpcPropertyAccessor");
return EmitCreateAccessorFactoryDelegate(context, codeGen);
}
public IXamlIlType EmitLoadAvaloniaPropertyAccessorFactory(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public IXamlType EmitLoadAvaloniaPropertyAccessorFactory(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.Ldnull();
EmitLoadPropertyAccessorFactory(context, codeGen, context.GetAvaloniaTypes().PropertyInfoAccessorFactory, "CreateAvaloniaPropertyAccessor");
return EmitCreateAccessorFactoryDelegate(context, codeGen);
}
private void EmitLoadPropertyAccessorFactory(XamlIlEmitContext context, IXamlIlEmitter codeGen, IXamlIlType type, string accessorFactoryName, bool isStatic = true)
private void EmitLoadPropertyAccessorFactory(XamlIlEmitContext context, IXamlILEmitter codeGen, IXamlType type, string accessorFactoryName, bool isStatic = true)
{
var types = context.GetAvaloniaTypes();
var weakReferenceType = context.Configuration.TypeSystem.GetType("System.WeakReference`1").MakeGenericType(context.Configuration.WellKnownTypes.Object);
@ -44,7 +48,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
codeGen.Ldftn(type.GetMethod(accessorFactorySignature));
}
public IXamlIlType EmitLoadIndexerAccessorFactory(XamlIlEmitContext context, IXamlIlEmitter codeGen, IXamlIlAstValueNode value)
public IXamlType EmitLoadIndexerAccessorFactory(XamlIlEmitContext context, IXamlILEmitter codeGen, IXamlAstValueNode value)
{
var intType = context.Configuration.TypeSystem.GetType("System.Int32");
if (_indexerClosureType is null)
@ -53,12 +57,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
context.Emit(value, codeGen, intType);
codeGen.Newobj(_indexerClosureType.FindConstructor(new List<IXamlIlType> { intType }));
codeGen.Newobj(_indexerClosureType.FindConstructor(new List<IXamlType> { intType }));
EmitLoadPropertyAccessorFactory(context, codeGen, _indexerClosureType, IndexerClosureFactoryMethodName, isStatic: false);
return EmitCreateAccessorFactoryDelegate(context, codeGen);
}
private IXamlIlType InitializeClosureType(XamlIlEmitContext context)
private IXamlType InitializeClosureType(XamlIlEmitContext context)
{
var types = context.GetAvaloniaTypes();
var intType = context.Configuration.TypeSystem.GetType("System.Int32");
@ -97,7 +101,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return _indexerClosureTypeBuilder.CreateType();
}
private IXamlIlType EmitCreateAccessorFactoryDelegate(XamlIlEmitContext context, IXamlIlEmitter codeGen)
private IXamlType EmitCreateAccessorFactoryDelegate(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
var types = context.GetAvaloniaTypes();
var weakReferenceType = context.Configuration.TypeSystem.GetType("System.WeakReference`1").MakeGenericType(context.Configuration.WellKnownTypes.Object);

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

@ -1 +1 @@
Subproject commit 068162245473ec39ee36da12150e928072b96403
Subproject commit 0028377ce7c7dc21f9fe71b45f62a95991b1ab58

2
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -569,7 +569,7 @@ namespace Avalonia.Skia
float constraint = -1;
if (_wrapping == TextWrapping.Wrap)
if (_wrapping != TextWrapping.NoWrap)
{
constraint = widthConstraint <= 0 ? MAX_LINE_WIDTH : widthConstraint;
if (constraint > MAX_LINE_WIDTH)

14
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -1,9 +1,9 @@
using System;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utility;
using Avalonia.Utilities;
using HarfBuzzSharp;
using Buffer = HarfBuzzSharp.Buffer;
@ -11,7 +11,7 @@ namespace Avalonia.Skia
{
internal class TextShaperImpl : ITextShaperImpl
{
public GlyphRun ShapeText(ReadOnlySlice<char> text, TextFormat textFormat)
public GlyphRun ShapeText(ReadOnlySlice<char> text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture)
{
using (var buffer = new Buffer())
{
@ -61,9 +61,11 @@ namespace Avalonia.Skia
buffer.AddUtf16(text.Buffer.Span);
}
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
buffer.GuessSegmentProperties();
var glyphTypeface = textFormat.Typeface.GlyphTypeface;
var glyphTypeface = typeface.GlyphTypeface;
var font = ((GlyphTypefaceImpl)glyphTypeface.PlatformImpl).Font;
@ -71,7 +73,7 @@ namespace Avalonia.Skia
font.GetScale(out var scaleX, out _);
var textScale = textFormat.FontRenderingEmSize / scaleX;
var textScale = fontRenderingEmSize / scaleX;
var bufferLength = buffer.Length;
@ -101,7 +103,7 @@ namespace Avalonia.Skia
SetOffset(glyphPositions, i, textScale, ref glyphOffsets);
}
return new GlyphRun(glyphTypeface, textFormat.FontRenderingEmSize,
return new GlyphRun(glyphTypeface, fontRenderingEmSize,
new ReadOnlySlice<ushort>(glyphIndices),
new ReadOnlySlice<double>(glyphAdvances),
new ReadOnlySlice<Vector>(glyphOffsets),

15
src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs

@ -1,8 +1,9 @@
using Avalonia.Media;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utility;
using Avalonia.Utilities;
using HarfBuzzSharp;
using Buffer = HarfBuzzSharp.Buffer;
@ -10,7 +11,7 @@ namespace Avalonia.Direct2D1.Media
{
internal class TextShaperImpl : ITextShaperImpl
{
public GlyphRun ShapeText(ReadOnlySlice<char> text, TextFormat textFormat)
public GlyphRun ShapeText(ReadOnlySlice<char> text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture)
{
using (var buffer = new Buffer())
{
@ -62,15 +63,17 @@ namespace Avalonia.Direct2D1.Media
buffer.GuessSegmentProperties();
var glyphTypeface = textFormat.Typeface.GlyphTypeface;
var glyphTypeface = typeface.GlyphTypeface;
var font = ((GlyphTypefaceImpl)glyphTypeface.PlatformImpl).Font;
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
font.Shape(buffer);
font.GetScale(out var scaleX, out _);
var textScale = textFormat.FontRenderingEmSize / scaleX;
var textScale = fontRenderingEmSize / scaleX;
var len = buffer.Length;
@ -104,7 +107,7 @@ namespace Avalonia.Direct2D1.Media
glyphOffsets[i] = new Vector(offsetX, offsetY);
}
return new GlyphRun(glyphTypeface, textFormat.FontRenderingEmSize,
return new GlyphRun(glyphTypeface, fontRenderingEmSize,
new ReadOnlySlice<ushort>(glyphIndices),
new ReadOnlySlice<double>(glyphAdvances),
new ReadOnlySlice<Vector>(glyphOffsets),

4
tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs

@ -41,7 +41,7 @@ namespace Avalonia.Direct2D1.UnitTests.Media
var fontManager = new FontManagerImpl();
var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface(
new Typeface(new FontFamily("A, B, Arial"), FontWeight.Bold));
new Typeface(new FontFamily("A, B, Arial"), weight: FontWeight.Bold));
var font = glyphTypeface.DWFont;
@ -105,7 +105,7 @@ namespace Avalonia.Direct2D1.UnitTests.Media
var fontManager = new FontManagerImpl();
var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface(
new Typeface(s_fontUri, FontWeight.Black, FontStyle.Italic));
new Typeface(s_fontUri, FontStyle.Italic, FontWeight.Black));
var font = glyphTypeface.DWFont;

12
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -9,7 +9,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Data.Core;
using Avalonia.Markup.Data;
using Avalonia.UnitTests;
using XamlIl;
using XamlX;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
@ -328,7 +328,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
<ContentControl Name='target' Content='{CompiledBinding}' />
</Window>";
var loader = new AvaloniaXamlLoader();
Assert.Throws<XamlIlTransformException>(() => loader.Load(xaml));
Assert.Throws<XamlTransformException>(() => loader.Load(xaml));
}
}
@ -347,7 +347,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
</ContentControl>
</Window>";
var loader = new AvaloniaXamlLoader();
Assert.Throws<XamlIlTransformException>(() => loader.Load(xaml));
Assert.Throws<XamlTransformException>(() => loader.Load(xaml));
}
}
@ -406,7 +406,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
</ItemsControl>
</Window>";
var loader = new AvaloniaXamlLoader();
Assert.Throws<XamlIlTransformException>(() => loader.Load(xaml));
Assert.Throws<XamlTransformException>(() => loader.Load(xaml));
}
}
@ -562,7 +562,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
<TextBlock Text='{Binding InvalidPath}' Name='textBlock' />
</Window>";
var loader = new AvaloniaXamlLoader();
Assert.Throws<XamlIlParseException>(() => loader.Load(xaml));
Assert.Throws<XamlX.XamlParseException>(() => loader.Load(xaml));
}
}
@ -579,7 +579,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
x:CompileBindings='notabool'>
</Window>";
var loader = new AvaloniaXamlLoader();
Assert.Throws<XamlIlParseException>(() => loader.Load(xaml));
Assert.Throws<XamlX.XamlParseException>(() => loader.Load(xaml));
}
}
}

45
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -249,51 +249,6 @@ namespace Avalonia.Markup.Xaml.UnitTests
Assert.Equal(6, parsed.Count);
}
[Fact]
public void Should_Provide_PropertyPath_For_Setters()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parsed = AvaloniaXamlLoader.Parse<Style>(@"
<Style Selector='Animatable' xmlns='https://github.com/avaloniaui'>
<Setter PropertyPath=':>Visual.Bounds.BottomRight.X' Value='0' />
<Setter PropertyPath=':>Visual.RenderTransform:=ScaleTransform.ScaleX' Value='0' />
<Setter PropertyPath='(Visual.RenderTransform):=ScaleTransform.ScaleX' Value='0' />
</Style>");
var s1e = ((Setter)parsed.Setters[0]).PropertyPath.Elements;
var s2e = ((Setter)parsed.Setters[1]).PropertyPath.Elements;
var s3e = ((Setter)parsed.Setters[2]).PropertyPath.Elements;
Assert.Equal(typeof(Visual), ((CastTypePropertyPathElement)s1e[0]).Type);
Assert.IsType<ChildTraversalPropertyPathElement>(s1e[1]);
Assert.Equal("Bounds", ((AvaloniaProperty)((PropertyPropertyPathElement)s1e[2]).Property).Name);
Assert.IsType<ChildTraversalPropertyPathElement>(s1e[3]);
var bottomRight = ((PropertyPropertyPathElement)s1e[4]).Property;
Assert.IsType<ChildTraversalPropertyPathElement>(s1e[5]);
var pointX = ((PropertyPropertyPathElement)s1e[6]).Property;
var orect = (object)(new Rect(100, 100, 200, 200));
var point = bottomRight.Get(orect);
var x = pointX.Get(point);
Assert.Equal(300, (double)x);
Assert.Equal(typeof(Visual), ((CastTypePropertyPathElement)s2e[0]).Type);
Assert.IsType<ChildTraversalPropertyPathElement>(s2e[1]);
Assert.Equal("RenderTransform", ((AvaloniaProperty)((PropertyPropertyPathElement)s2e[2]).Property).Name);
Assert.Equal(typeof(ScaleTransform), ((EnsureTypePropertyPathElement)s2e[3]).Type);
Assert.IsType<ChildTraversalPropertyPathElement>(s2e[4]);
Assert.Equal("ScaleX", ((AvaloniaProperty)((PropertyPropertyPathElement)s2e[5]).Property).Name);
var s3fqp = (AvaloniaProperty)((PropertyPropertyPathElement)s3e[0]).Property;
Assert.Equal("RenderTransform", s3fqp.Name);
Assert.Equal(typeof(Visual), s3fqp.OwnerType);
Assert.Equal(typeof(ScaleTransform), ((EnsureTypePropertyPathElement)s3e[1]).Type);
Assert.IsType<ChildTraversalPropertyPathElement>(s3e[2]);
Assert.Equal("ScaleX", ((AvaloniaProperty)((PropertyPropertyPathElement)s3e[3]).Property).Name);
}
}
[Fact]
public void DataContextType_Resolution()
{

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

@ -6,7 +6,7 @@ using Avalonia.Media.Fonts;
using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia.UnitTests
namespace Avalonia.Skia.UnitTests.Media
{
public class CustomFontManagerImpl : IFontManagerImpl
{

8
tests/Avalonia.Skia.UnitTests/FontManagerImplTests.cs → tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs

@ -1,13 +1,11 @@
using System;
using System.Linq;
using System.Reflection;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests;
using SkiaSharp;
using Xunit;
namespace Avalonia.Skia.UnitTests
namespace Avalonia.Skia.UnitTests.Media
{
public class FontManagerImplTests
{
@ -39,7 +37,7 @@ namespace Avalonia.Skia.UnitTests
string fontName = fontManager.GetInstalledFontFamilyNames().First();
var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface(
new Typeface(new FontFamily($"A, B, {fontName}"), FontWeight.Bold));
new Typeface(new FontFamily($"A, B, {fontName}"), weight: FontWeight.Bold));
var skTypeface = glyphTypeface.Typeface;
@ -88,7 +86,7 @@ namespace Avalonia.Skia.UnitTests
var fontManager = new FontManagerImpl();
var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface(
new Typeface(s_fontUri, FontWeight.Black, FontStyle.Italic));
new Typeface(s_fontUri, FontStyle.Italic, FontWeight.Black));
var skTypeface = glyphTypeface.Typeface;

4
tests/Avalonia.Skia.UnitTests/SKTypefaceCollectionCacheTests.cs → tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs

@ -2,7 +2,7 @@
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Skia.UnitTests
namespace Avalonia.Skia.UnitTests.Media
{
public class SKTypefaceCollectionCacheTests
{
@ -19,7 +19,7 @@ namespace Avalonia.Skia.UnitTests
var notoMonoCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(notoMono);
var typeface = new Typeface("ABC", FontWeight.Bold, FontStyle.Italic);
var typeface = new Typeface("ABC", FontStyle.Italic, FontWeight.Bold);
Assert.Equal("Noto Mono", notoMonoCollection.Get(typeface).FamilyName);

38
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/FormattableTextSource.cs

@ -0,0 +1,38 @@
using System;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
internal class FormattableTextSource : ITextSource
{
private readonly ReadOnlySlice<char> _text;
private readonly TextRunProperties _defaultStyle;
private ReadOnlySlice<ValueSpan<TextRunProperties>> _styleSpans;
public FormattableTextSource(string text, TextRunProperties defaultStyle,
ReadOnlySlice<ValueSpan<TextRunProperties>> styleSpans)
{
_text = text.AsMemory();
_defaultStyle = defaultStyle;
_styleSpans = styleSpans;
}
public TextRun GetTextRun(int textSourceIndex)
{
if (_styleSpans.IsEmpty)
{
return new TextEndOfParagraph();
}
var currentSpan = _styleSpans[0];
_styleSpans = _styleSpans.Skip(1);
return new TextCharacters(_text.AsSlice(currentSpan.Start, currentSpan.Length),
_defaultStyle);
}
}
}

36
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs

@ -0,0 +1,36 @@
using System;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
internal class MultiBufferTextSource : ITextSource
{
private readonly string[] _runTexts;
private readonly GenericTextRunProperties _defaultStyle;
public MultiBufferTextSource(GenericTextRunProperties defaultStyle)
{
_defaultStyle = defaultStyle;
_runTexts = new[] { "A123456789", "B123456789", "C123456789", "D123456789", "E123456789" };
}
public static TextRange TextRange => new TextRange(0, 50);
public TextRun GetTextRun(int textSourceIndex)
{
if (textSourceIndex == 50)
{
return new TextEndOfParagraph();
}
var index = textSourceIndex / 10;
var runText = _runTexts[index];
return new TextCharacters(
new ReadOnlySlice<char>(runText.AsMemory(), textSourceIndex, runText.Length), _defaultStyle);
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save