diff --git a/README.md b/README.md
index 92180ca76d..38790aa5cc 100644
--- a/README.md
+++ b/README.md
@@ -230,6 +230,14 @@ Install-Package Svg.Controls.Skia.Avalonia
```
+#### Background
+
+```XAML
+
+```
+
### CSS styling
```XAML
@@ -265,6 +273,29 @@ Install-Package Svg.Controls.Skia.Avalonia
```
+#### SvgBrush Markup Extension
+
+Use `SvgBrush` when you want to place an SVG-backed `VisualBrush` in a resource dictionary and reuse it for backgrounds or other brush targets:
+
+```XAML
+
+
+ /Assets/__tiger.svg
+
+
+
+
+```
+
+The optional properties mirror those on `VisualBrush`, so you can tweak layout, tiling, opacity, and transforms directly in XAML while the control takes care of loading and rendering the SVG content.
+
#### Avalonia Previewer
To make controls work with `Avalonia Previewer` please add the following lines to `BuildAvaloniaApp()` method:
diff --git a/Svg.Skia.sln b/Svg.Skia.sln
index 25d26c8a86..b21e3509c7 100644
--- a/Svg.Skia.sln
+++ b/Svg.Skia.sln
@@ -77,6 +77,8 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Custom", "src\Svg.Custom\Svg.Custom.csproj", "{CFA46E73-0050-4C57-85CE-6C5868A2483C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Controls.Skia.Avalonia.UnitTests", "tests\Svg.Controls.Skia.Avalonia.UnitTests\Svg.Controls.Skia.Avalonia.UnitTests.csproj", "{D4467DCA-494D-4C32-9525-4A9713221A53}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Controls.Avalonia.UnitTests", "tests\Svg.Controls.Avalonia.UnitTests\Svg.Controls.Avalonia.UnitTests.csproj", "{55A5979A-292D-4A51-A89E-14FCEF5E6792}"
+EndProject
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Model", "src\Svg.Model\Svg.Model.csproj", "{4C970B2C-6C96-445B-B80B-4EFBF803FD5F}"
EndProject
@@ -157,6 +159,10 @@ Global
{D4467DCA-494D-4C32-9525-4A9713221A53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4467DCA-494D-4C32-9525-4A9713221A53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4467DCA-494D-4C32-9525-4A9713221A53}.Release|Any CPU.Build.0 = Release|Any CPU
+ {55A5979A-292D-4A51-A89E-14FCEF5E6792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {55A5979A-292D-4A51-A89E-14FCEF5E6792}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {55A5979A-292D-4A51-A89E-14FCEF5E6792}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {55A5979A-292D-4A51-A89E-14FCEF5E6792}.Release|Any CPU.Build.0 = Release|Any CPU
{4C970B2C-6C96-445B-B80B-4EFBF803FD5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C970B2C-6C96-445B-B80B-4EFBF803FD5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C970B2C-6C96-445B-B80B-4EFBF803FD5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -241,6 +247,7 @@ Global
{81724F00-B7C3-4E25-B473-C7433BABDC81} = {B65D5B3A-77BE-4AFF-B502-A136B9C932F8}
{CFA46E73-0050-4C57-85CE-6C5868A2483C} = {C5FFCF4B-86DC-453E-8006-44EE9EEFEE39}
{D4467DCA-494D-4C32-9525-4A9713221A53} = {7863AE7D-FF68-45BF-BA68-6FA0E5604CB7}
+ {55A5979A-292D-4A51-A89E-14FCEF5E6792} = {7863AE7D-FF68-45BF-BA68-6FA0E5604CB7}
{4C970B2C-6C96-445B-B80B-4EFBF803FD5F} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18}
{29F59C87-EAE6-4DD3-8666-B79BFAF6B34D} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18}
{223B7A5A-E263-4D40-9A6E-FE31EAE92F45} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18}
diff --git a/samples/AvaloniaSvgSample/MainWindow.axaml b/samples/AvaloniaSvgSample/MainWindow.axaml
index 0ad439b3c2..bd01435bc7 100644
--- a/samples/AvaloniaSvgSample/MainWindow.axaml
+++ b/samples/AvaloniaSvgSample/MainWindow.axaml
@@ -105,6 +105,23 @@
+
+
+
+
+
+
+
+
+ /Assets/__tiger.svg
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /Assets/__tiger.svg
+
+
+
+
+
+
+/// Provides an SVG-backed brush that can be declared in XAML resources.
+///
+public class SvgBrush : MarkupExtension
+{
+ ///
+ /// Gets or sets the SVG resource or file path.
+ ///
+ [Content]
+ public string? Path { get; set; }
+
+ ///
+ /// Gets or sets the stretch applied to the resulting brush.
+ ///
+ public Stretch? Stretch { get; set; }
+
+ ///
+ /// Gets or sets the horizontal alignment applied to the resulting brush.
+ ///
+ public AlignmentX? AlignmentX { get; set; }
+
+ ///
+ /// Gets or sets the vertical alignment applied to the resulting brush.
+ ///
+ public AlignmentY? AlignmentY { get; set; }
+
+ ///
+ /// Gets or sets the tile mode applied to the resulting brush.
+ ///
+ public TileMode? TileMode { get; set; }
+
+ ///
+ /// Gets or sets the destination rectangle applied to the resulting brush.
+ ///
+ public RelativeRect? DestinationRect { get; set; }
+
+ ///
+ /// Gets or sets the source rectangle applied to the resulting brush.
+ ///
+ public RelativeRect? SourceRect { get; set; }
+
+ ///
+ /// Gets or sets the opacity applied to the resulting brush.
+ ///
+ public double? Opacity { get; set; }
+
+ ///
+ /// Gets or sets the transform applied to the resulting brush.
+ ///
+ public Transform? Transform { get; set; }
+
+ ///
+ /// Gets or sets the transform origin applied to the resulting brush.
+ ///
+ public RelativePoint? TransformOrigin { get; set; }
+
+ ///
+ /// Creates a configured with the supplied image and optional overrides.
+ ///
+ /// The SVG image instance rendered by the brush.
+ /// Optional stretch applied to the brush.
+ /// Optional horizontal alignment applied to the brush.
+ /// Optional vertical alignment applied to the brush.
+ /// Optional tile mode applied to the brush.
+ /// Optional destination rectangle for the brush content.
+ /// Optional source rectangle cropping the brush content.
+ /// Optional opacity multiplier applied to the brush.
+ /// Optional transform applied to the brush.
+ /// Optional transform origin applied when is set.
+ /// A that renders .
+ internal static IBrush CreateFromImage(
+ IImage image,
+ Stretch? stretch = null,
+ AlignmentX? alignmentX = null,
+ AlignmentY? alignmentY = null,
+ TileMode? tileMode = null,
+ RelativeRect? destinationRect = null,
+ RelativeRect? sourceRect = null,
+ double? opacity = null,
+ Transform? transform = null,
+ RelativePoint? transformOrigin = null)
+ {
+ var brush = new VisualBrush
+ {
+ Visual = new Image
+ {
+ Source = image
+ }
+ };
+
+ if (stretch.HasValue)
+ {
+ brush.Stretch = stretch.Value;
+ }
+
+ if (alignmentX.HasValue)
+ {
+ brush.AlignmentX = alignmentX.Value;
+ }
+
+ if (alignmentY.HasValue)
+ {
+ brush.AlignmentY = alignmentY.Value;
+ }
+
+ if (tileMode.HasValue)
+ {
+ brush.TileMode = tileMode.Value;
+ }
+
+ if (destinationRect.HasValue)
+ {
+ brush.DestinationRect = destinationRect.Value;
+ }
+
+ if (sourceRect.HasValue)
+ {
+ brush.SourceRect = sourceRect.Value;
+ }
+
+ if (opacity.HasValue)
+ {
+ brush.Opacity = opacity.Value;
+ }
+
+ if (transform is not null)
+ {
+ brush.Transform = transform;
+ }
+
+ if (transformOrigin.HasValue)
+ {
+ brush.TransformOrigin = transformOrigin.Value;
+ }
+
+ return brush;
+ }
+
+ ///
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (serviceProvider is null)
+ {
+ throw new ArgumentNullException(nameof(serviceProvider));
+ }
+
+ if (Path is null)
+ {
+ throw new InvalidOperationException("SvgBrush requires a non-null Path.");
+ }
+
+ var baseUri = serviceProvider.GetContextBaseUri();
+ var source = SvgSource.Load(Path, baseUri);
+ var image = new SvgImage
+ {
+ Source = source
+ };
+
+ return CreateFromImage(
+ image,
+ Stretch,
+ AlignmentX,
+ AlignmentY,
+ TileMode,
+ DestinationRect,
+ SourceRect,
+ Opacity,
+ Transform,
+ TransformOrigin);
+ }
+}
diff --git a/src/Svg.Controls.Avalonia/SvgImageExtension.cs b/src/Svg.Controls.Avalonia/SvgImageExtension.cs
index b0f30401c7..72acdea6a1 100644
--- a/src/Svg.Controls.Avalonia/SvgImageExtension.cs
+++ b/src/Svg.Controls.Avalonia/SvgImageExtension.cs
@@ -31,14 +31,23 @@ public override object ProvideValue(IServiceProvider serviceProvider)
var baseUri = context.BaseUri;
var source = SvgSource.Load(path, baseUri);
var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget))!;
- if (target.TargetProperty is AvaloniaProperty property)
+ var image = new SvgImage { Source = source };
+
+ if (target.TargetProperty is not AvaloniaProperty property)
+ {
+ return image;
+ }
+
+ if (typeof(IImage).IsAssignableFrom(property.PropertyType))
{
- if (property.PropertyType == typeof(IImage))
- {
- return new SvgImage { Source = source };
- }
- return new Image { Source = new SvgImage { Source = source } };
+ return image;
}
- return new SvgImage { Source = source };
+
+ if (typeof(IBrush).IsAssignableFrom(property.PropertyType))
+ {
+ return SvgBrush.CreateFromImage(image);
+ }
+
+ return new Image { Source = image };
}
}
diff --git a/src/Svg.Controls.Skia.Avalonia/SvgBrush.cs b/src/Svg.Controls.Skia.Avalonia/SvgBrush.cs
new file mode 100644
index 0000000000..8e5b7426e2
--- /dev/null
+++ b/src/Svg.Controls.Skia.Avalonia/SvgBrush.cs
@@ -0,0 +1,218 @@
+// Copyright (c) Wiesław Šoltés. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for details.
+using System;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Metadata;
+using Svg.Model;
+
+namespace Avalonia.Svg.Skia;
+
+///
+/// Provides an SVG-backed brush that can be declared in XAML resources.
+///
+public class SvgBrush : MarkupExtension
+{
+ ///
+ /// Gets or sets the SVG resource or file path.
+ ///
+ [Content]
+ public string? Path { get; set; }
+
+ ///
+ /// Gets or sets the CSS applied when loading the SVG resource.
+ ///
+ public string? Css { get; set; }
+
+ ///
+ /// Gets or sets the current CSS applied when loading the SVG resource.
+ ///
+ public string? CurrentCss { get; set; }
+
+ ///
+ /// Gets or sets the stretch applied to the resulting brush.
+ ///
+ public Stretch? Stretch { get; set; }
+
+ ///
+ /// Gets or sets the horizontal alignment applied to the resulting brush.
+ ///
+ public AlignmentX? AlignmentX { get; set; }
+
+ ///
+ /// Gets or sets the vertical alignment applied to the resulting brush.
+ ///
+ public AlignmentY? AlignmentY { get; set; }
+
+ ///
+ /// Gets or sets the tile mode applied to the resulting brush.
+ ///
+ public TileMode? TileMode { get; set; }
+
+ ///
+ /// Gets or sets the destination rectangle applied to the resulting brush.
+ ///
+ public RelativeRect? DestinationRect { get; set; }
+
+ ///
+ /// Gets or sets the source rectangle applied to the resulting brush.
+ ///
+ public RelativeRect? SourceRect { get; set; }
+
+ ///
+ /// Gets or sets the opacity applied to the resulting brush.
+ ///
+ public double? Opacity { get; set; }
+
+ ///
+ /// Gets or sets the transform applied to the resulting brush.
+ ///
+ public Transform? Transform { get; set; }
+
+ ///
+ /// Gets or sets the transform origin applied to the resulting brush.
+ ///
+ public RelativePoint? TransformOrigin { get; set; }
+
+ ///
+ /// Creates a configured with the provided image and optional overrides.
+ ///
+ /// The SVG image instance rendered by the brush.
+ /// Optional stretch applied to the brush.
+ /// Optional horizontal alignment applied to the brush.
+ /// Optional vertical alignment applied to the brush.
+ /// Optional tile mode applied to the brush.
+ /// Optional destination rectangle for the brush content.
+ /// Optional source rectangle cropping the brush content.
+ /// Optional opacity multiplier applied to the brush.
+ /// Optional transform applied to the brush.
+ /// Optional transform origin applied when is set.
+ /// A that renders .
+ internal static IBrush CreateFromImage(
+ IImage image,
+ Stretch? stretch = null,
+ AlignmentX? alignmentX = null,
+ AlignmentY? alignmentY = null,
+ TileMode? tileMode = null,
+ RelativeRect? destinationRect = null,
+ RelativeRect? sourceRect = null,
+ double? opacity = null,
+ Transform? transform = null,
+ RelativePoint? transformOrigin = null)
+ {
+ var brush = new VisualBrush
+ {
+ Visual = new Image
+ {
+ Source = image
+ }
+ };
+
+ if (stretch.HasValue)
+ {
+ brush.Stretch = stretch.Value;
+ }
+
+ if (alignmentX.HasValue)
+ {
+ brush.AlignmentX = alignmentX.Value;
+ }
+
+ if (alignmentY.HasValue)
+ {
+ brush.AlignmentY = alignmentY.Value;
+ }
+
+ if (tileMode.HasValue)
+ {
+ brush.TileMode = tileMode.Value;
+ }
+
+ if (destinationRect.HasValue)
+ {
+ brush.DestinationRect = destinationRect.Value;
+ }
+
+ if (sourceRect.HasValue)
+ {
+ brush.SourceRect = sourceRect.Value;
+ }
+
+ if (opacity.HasValue)
+ {
+ brush.Opacity = opacity.Value;
+ }
+
+ if (transform is not null)
+ {
+ brush.Transform = transform;
+ }
+
+ if (transformOrigin.HasValue)
+ {
+ brush.TransformOrigin = transformOrigin.Value;
+ }
+
+ return brush;
+ }
+
+ ///
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (serviceProvider is null)
+ {
+ throw new ArgumentNullException(nameof(serviceProvider));
+ }
+
+ if (Path is null)
+ {
+ throw new InvalidOperationException("SvgBrush requires a non-null Path.");
+ }
+
+ var baseUri = serviceProvider.GetContextBaseUri();
+ var parameters = CreateParameters(Css, CurrentCss);
+ var source = SvgSource.Load(Path, baseUri, parameters);
+ var image = new SvgImage
+ {
+ Source = source,
+ Css = Css,
+ CurrentCss = CurrentCss
+ };
+
+ return CreateFromImage(
+ image,
+ Stretch,
+ AlignmentX,
+ AlignmentY,
+ TileMode,
+ DestinationRect,
+ SourceRect,
+ Opacity,
+ Transform,
+ TransformOrigin);
+ }
+
+ private static SvgParameters? CreateParameters(string? css, string? currentCss)
+ {
+ var combined = CombineCss(css, currentCss);
+ return string.IsNullOrWhiteSpace(combined)
+ ? null
+ : new SvgParameters(null, combined);
+ }
+
+ private static string? CombineCss(string? css, string? currentCss)
+ {
+ if (string.IsNullOrWhiteSpace(css))
+ {
+ return string.IsNullOrWhiteSpace(currentCss) ? null : currentCss;
+ }
+
+ if (string.IsNullOrWhiteSpace(currentCss))
+ {
+ return css;
+ }
+
+ return string.Concat(css, ' ', currentCss);
+ }
+}
diff --git a/src/Svg.Controls.Skia.Avalonia/SvgImageExtension.cs b/src/Svg.Controls.Skia.Avalonia/SvgImageExtension.cs
index 9d3d638324..00ddfa6e76 100644
--- a/src/Svg.Controls.Skia.Avalonia/SvgImageExtension.cs
+++ b/src/Svg.Controls.Skia.Avalonia/SvgImageExtension.cs
@@ -39,11 +39,16 @@ public override object ProvideValue(IServiceProvider serviceProvider)
return image;
}
- if (property.PropertyType == typeof(IImage))
+ if (typeof(IImage).IsAssignableFrom(property.PropertyType))
{
return image;
}
+ if (typeof(IBrush).IsAssignableFrom(property.PropertyType))
+ {
+ return SvgBrush.CreateFromImage(image);
+ }
+
return new Image { Source = image };
}
diff --git a/tests/Svg.Controls.Avalonia.UnitTests/Assets/Icon.svg b/tests/Svg.Controls.Avalonia.UnitTests/Assets/Icon.svg
new file mode 100644
index 0000000000..382d97f02f
--- /dev/null
+++ b/tests/Svg.Controls.Avalonia.UnitTests/Assets/Icon.svg
@@ -0,0 +1,31 @@
+
diff --git a/tests/Svg.Controls.Avalonia.UnitTests/Svg.Controls.Avalonia.UnitTests.csproj b/tests/Svg.Controls.Avalonia.UnitTests/Svg.Controls.Avalonia.UnitTests.csproj
new file mode 100644
index 0000000000..d9696b5e74
--- /dev/null
+++ b/tests/Svg.Controls.Avalonia.UnitTests/Svg.Controls.Avalonia.UnitTests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net9.0
+ Library
+ False
+ enable
+ Avalonia.Svg.UnitTests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Svg.Controls.Avalonia.UnitTests/SvgImageTests.cs b/tests/Svg.Controls.Avalonia.UnitTests/SvgImageTests.cs
new file mode 100644
index 0000000000..81f221d279
--- /dev/null
+++ b/tests/Svg.Controls.Avalonia.UnitTests/SvgImageTests.cs
@@ -0,0 +1,56 @@
+using Avalonia;
+using Avalonia.Headless.XUnit;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Svg;
+using Avalonia.Svg.UnitTests.Views;
+using Xunit;
+
+namespace Avalonia.Svg.UnitTests;
+
+public class SvgImageTests
+{
+ [AvaloniaFact]
+ public void SvgImageExtension_Returns_VisualBrush_For_Brush_Property()
+ {
+ var view = new SvgImageBackgroundView();
+ var host = Assert.IsType(view.BackgroundHost);
+
+ var brush = Assert.IsType(host.Background);
+ var image = Assert.IsType(brush.Visual);
+ var svgImage = Assert.IsType(image.Source);
+ Assert.NotNull(svgImage.Source);
+ }
+
+ [AvaloniaFact]
+ public void SvgBrushResource_Returns_VisualBrush()
+ {
+ var view = new SvgBrushBackgroundView();
+ var host = Assert.IsType(view.BackgroundHost);
+
+ var brush = Assert.IsType(host.Background);
+ var image = Assert.IsType(brush.Visual);
+ var svgImage = Assert.IsType(image.Source);
+ Assert.NotNull(svgImage.Source);
+ Assert.Equal(Stretch.UniformToFill, brush.Stretch);
+ Assert.Equal(AlignmentX.Center, brush.AlignmentX);
+ Assert.Equal(AlignmentY.Bottom, brush.AlignmentY);
+ Assert.Equal(TileMode.Tile, brush.TileMode);
+ Assert.Equal(RelativeUnit.Absolute, brush.DestinationRect.Unit);
+ Assert.Equal(RelativeUnit.Absolute, brush.SourceRect.Unit);
+ Assert.Equal(0.1, brush.DestinationRect.Rect.X, 6);
+ Assert.Equal(0.2, brush.DestinationRect.Rect.Y, 6);
+ Assert.Equal(0.6, brush.DestinationRect.Rect.Width, 6);
+ Assert.Equal(0.7, brush.DestinationRect.Rect.Height, 6);
+ Assert.Equal(0.05, brush.SourceRect.Rect.X, 6);
+ Assert.Equal(0.05, brush.SourceRect.Rect.Y, 6);
+ Assert.Equal(0.9, brush.SourceRect.Rect.Width, 6);
+ Assert.Equal(0.9, brush.SourceRect.Rect.Height, 6);
+ Assert.Equal(0.5, brush.Opacity);
+ var matrixTransform = Assert.IsType(brush.Transform);
+ Assert.Equal(new Matrix(1, 0, 0, 1, 10, 20), matrixTransform.Matrix);
+ Assert.Equal(RelativeUnit.Absolute, brush.TransformOrigin.Unit);
+ Assert.Equal(0.25, brush.TransformOrigin.Point.X, 6);
+ Assert.Equal(0.75, brush.TransformOrigin.Point.Y, 6);
+ }
+}
diff --git a/tests/Svg.Controls.Avalonia.UnitTests/TestApplication.cs b/tests/Svg.Controls.Avalonia.UnitTests/TestApplication.cs
new file mode 100644
index 0000000000..cc86a11e7d
--- /dev/null
+++ b/tests/Svg.Controls.Avalonia.UnitTests/TestApplication.cs
@@ -0,0 +1,24 @@
+using Avalonia;
+using Avalonia.Headless;
+using Avalonia.Platform;
+
+[assembly: Avalonia.Headless.AvaloniaTestApplication(typeof(Avalonia.Svg.UnitTests.SvgControlsAvaloniaTestsAppBuilder))]
+
+namespace Avalonia.Svg.UnitTests;
+
+internal static class SvgControlsAvaloniaTestsAppBuilder
+{
+ public static AppBuilder BuildAvaloniaApp() =>
+ AppBuilder.Configure()
+ .UseHeadless(new AvaloniaHeadlessPlatformOptions())
+ .LogToTrace();
+}
+
+internal sealed class SvgControlsAvaloniaTestsApp : Application
+{
+ public override void OnFrameworkInitializationCompleted()
+ {
+ AssetLoader.SetDefaultAssembly(typeof(SvgControlsAvaloniaTestsApp).Assembly);
+ base.OnFrameworkInitializationCompleted();
+ }
+}
diff --git a/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml b/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml
new file mode 100644
index 0000000000..e9405f0acb
--- /dev/null
+++ b/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml
@@ -0,0 +1,19 @@
+
+
+ /Assets/Icon.svg
+
+
+
diff --git a/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml.cs b/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml.cs
new file mode 100644
index 0000000000..8b8e214b28
--- /dev/null
+++ b/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml.cs
@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Avalonia.Svg.UnitTests.Views;
+
+public partial class SvgBrushBackgroundView : UserControl
+{
+ public SvgBrushBackgroundView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml b/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml
new file mode 100644
index 0000000000..644fc099bd
--- /dev/null
+++ b/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml.cs b/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml.cs
new file mode 100644
index 0000000000..8cd6ea7335
--- /dev/null
+++ b/tests/Svg.Controls.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml.cs
@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Avalonia.Svg.UnitTests.Views;
+
+public partial class SvgImageBackgroundView : UserControl
+{
+ public SvgImageBackgroundView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/tests/Svg.Controls.Skia.Avalonia.UnitTests/Svg.Controls.Skia.Avalonia.UnitTests.csproj b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Svg.Controls.Skia.Avalonia.UnitTests.csproj
index 14dfcc9ff9..307b76f3ac 100644
--- a/tests/Svg.Controls.Skia.Avalonia.UnitTests/Svg.Controls.Skia.Avalonia.UnitTests.csproj
+++ b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Svg.Controls.Skia.Avalonia.UnitTests.csproj
@@ -23,4 +23,8 @@
+
+
+
+
diff --git a/tests/Svg.Controls.Skia.Avalonia.UnitTests/SvgImageTests.cs b/tests/Svg.Controls.Skia.Avalonia.UnitTests/SvgImageTests.cs
index 4491ede805..735fa3150f 100644
--- a/tests/Svg.Controls.Skia.Avalonia.UnitTests/SvgImageTests.cs
+++ b/tests/Svg.Controls.Skia.Avalonia.UnitTests/SvgImageTests.cs
@@ -1,24 +1,66 @@
-using System;
-using Avalonia.Platform;
+using Avalonia;
+using Avalonia.Headless.XUnit;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Svg.Skia;
+using Avalonia.Svg.Skia.UnitTests.Views;
using Xunit;
namespace Avalonia.Svg.Skia.UnitTests;
public class SvgImageTests
{
- [Fact]
+ [AvaloniaFact]
public void SvgImage_Load()
{
- var uri = new Uri($"avares://Svg.Controls.Skia.Avalonia.UnitTests/Assets/Icon.svg");
- var assetLoader = new StandardAssetLoader(); // AvaloniaLocator.Current.GetService()
-
- var svgFile = assetLoader.Open(uri);
- Assert.NotNull(svgFile);
-
- var svgSource = SvgSource.LoadFromStream(svgFile);
+ var uri = new System.Uri($"avares://{typeof(SvgImageTests).Assembly.GetName().Name}/Assets/Icon.svg");
+ using var stream = Avalonia.Platform.AssetLoader.Open(uri);
+ var svgSource = SvgSource.LoadFromStream(stream);
Assert.NotNull(svgSource);
var svgImage = new SvgImage() { Source = svgSource };
Assert.NotNull(svgImage);
}
+
+ [AvaloniaFact]
+ public void SvgImageExtension_Returns_VisualBrush_For_Brush_Property()
+ {
+ var view = new SvgImageBackgroundView();
+ var host = Assert.IsType(view.BackgroundHost);
+ var brush = Assert.IsType(host.Background);
+ var image = Assert.IsType(brush.Visual);
+ var svgImage = Assert.IsType(image.Source);
+ Assert.NotNull(svgImage.Source);
+ }
+
+ [AvaloniaFact]
+ public void SvgBrushResource_Returns_VisualBrush()
+ {
+ var view = new SvgBrushBackgroundView();
+ var host = Assert.IsType(view.BackgroundHost);
+ var brush = Assert.IsType(host.Background);
+ var image = Assert.IsType(brush.Visual);
+ var svgImage = Assert.IsType(image.Source);
+ Assert.NotNull(svgImage.Source);
+ Assert.Equal(Stretch.UniformToFill, brush.Stretch);
+ Assert.Equal(AlignmentX.Center, brush.AlignmentX);
+ Assert.Equal(AlignmentY.Bottom, brush.AlignmentY);
+ Assert.Equal(TileMode.Tile, brush.TileMode);
+ Assert.Equal(RelativeUnit.Absolute, brush.DestinationRect.Unit);
+ Assert.Equal(RelativeUnit.Absolute, brush.SourceRect.Unit);
+ Assert.Equal(0.1, brush.DestinationRect.Rect.X, 6);
+ Assert.Equal(0.2, brush.DestinationRect.Rect.Y, 6);
+ Assert.Equal(0.6, brush.DestinationRect.Rect.Width, 6);
+ Assert.Equal(0.7, brush.DestinationRect.Rect.Height, 6);
+ Assert.Equal(0.05, brush.SourceRect.Rect.X, 6);
+ Assert.Equal(0.05, brush.SourceRect.Rect.Y, 6);
+ Assert.Equal(0.9, brush.SourceRect.Rect.Width, 6);
+ Assert.Equal(0.9, brush.SourceRect.Rect.Height, 6);
+ Assert.Equal(0.5, brush.Opacity);
+ var matrixTransform = Assert.IsType(brush.Transform);
+ Assert.Equal(new Matrix(1, 0, 0, 1, 10, 20), matrixTransform.Matrix);
+ Assert.Equal(RelativeUnit.Absolute, brush.TransformOrigin.Unit);
+ Assert.Equal(0.25, brush.TransformOrigin.Point.X, 6);
+ Assert.Equal(0.75, brush.TransformOrigin.Point.Y, 6);
+ }
}
diff --git a/tests/Svg.Controls.Skia.Avalonia.UnitTests/TestApplication.cs b/tests/Svg.Controls.Skia.Avalonia.UnitTests/TestApplication.cs
new file mode 100644
index 0000000000..e5fc6fe66b
--- /dev/null
+++ b/tests/Svg.Controls.Skia.Avalonia.UnitTests/TestApplication.cs
@@ -0,0 +1,24 @@
+using Avalonia;
+using Avalonia.Headless;
+using Avalonia.Platform;
+
+[assembly: Avalonia.Headless.AvaloniaTestApplication(typeof(Avalonia.Svg.Skia.UnitTests.SvgControlsSkiaAvaloniaTestsAppBuilder))]
+
+namespace Avalonia.Svg.Skia.UnitTests;
+
+internal static class SvgControlsSkiaAvaloniaTestsAppBuilder
+{
+ public static AppBuilder BuildAvaloniaApp() =>
+ AppBuilder.Configure()
+ .UseHeadless(new AvaloniaHeadlessPlatformOptions())
+ .LogToTrace();
+}
+
+internal sealed class SvgControlsSkiaAvaloniaTestsApp : Application
+{
+ public override void OnFrameworkInitializationCompleted()
+ {
+ AssetLoader.SetDefaultAssembly(typeof(SvgControlsSkiaAvaloniaTestsApp).Assembly);
+ base.OnFrameworkInitializationCompleted();
+ }
+}
diff --git a/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml
new file mode 100644
index 0000000000..10d1785ec9
--- /dev/null
+++ b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml
@@ -0,0 +1,19 @@
+
+
+ /Assets/Icon.svg
+
+
+
diff --git a/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml.cs b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml.cs
new file mode 100644
index 0000000000..1585f2935b
--- /dev/null
+++ b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgBrushBackgroundView.axaml.cs
@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Avalonia.Svg.Skia.UnitTests.Views;
+
+public partial class SvgBrushBackgroundView : UserControl
+{
+ public SvgBrushBackgroundView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml
new file mode 100644
index 0000000000..37d9de1c3b
--- /dev/null
+++ b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml.cs b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml.cs
new file mode 100644
index 0000000000..58f6340acf
--- /dev/null
+++ b/tests/Svg.Controls.Skia.Avalonia.UnitTests/Views/SvgImageBackgroundView.axaml.cs
@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Avalonia.Svg.Skia.UnitTests.Views;
+
+public partial class SvgImageBackgroundView : UserControl
+{
+ public SvgImageBackgroundView()
+ {
+ InitializeComponent();
+ }
+}