소스 검색

Added defs and gradients

Krzysztof Krysiński 4 달 전
부모
커밋
3334b3a9f9
34개의 변경된 파일492개의 추가작업 그리고 92개의 파일을 삭제
  1. 1 1
      src/Drawie
  2. 48 0
      src/PixiEditor.SVG/Elements/SvgDefs.cs
  3. 17 7
      src/PixiEditor.SVG/Elements/SvgGroup.cs
  4. 2 2
      src/PixiEditor.SVG/Elements/SvgImage.cs
  5. 71 0
      src/PixiEditor.SVG/Elements/SvgLinearGradient.cs
  6. 2 2
      src/PixiEditor.SVG/Elements/SvgMask.cs
  7. 4 4
      src/PixiEditor.SVG/Elements/SvgPrimitive.cs
  8. 76 0
      src/PixiEditor.SVG/Elements/SvgRadialGradient.cs
  9. 28 0
      src/PixiEditor.SVG/Elements/SvgStop.cs
  10. 2 2
      src/PixiEditor.SVG/Elements/SvgText.cs
  11. 7 0
      src/PixiEditor.SVG/Enums/SvgGradientUnit.cs
  12. 8 0
      src/PixiEditor.SVG/Enums/SvgSpreadMethod.cs
  13. 8 0
      src/PixiEditor.SVG/Features/IDefsStorage.cs
  14. 1 1
      src/PixiEditor.SVG/Features/IFillable.cs
  15. 8 0
      src/PixiEditor.SVG/Features/IPaintServer.cs
  16. 1 1
      src/PixiEditor.SVG/Features/IStrokable.cs
  17. 15 8
      src/PixiEditor.SVG/StyleContext.cs
  18. 9 6
      src/PixiEditor.SVG/SvgDocument.cs
  19. 33 8
      src/PixiEditor.SVG/SvgElement.cs
  20. 41 13
      src/PixiEditor.SVG/SvgParser.cs
  21. 2 1
      src/PixiEditor.SVG/Units/SvgColorUnit.cs
  22. 2 1
      src/PixiEditor.SVG/Units/SvgEnumUnit.cs
  23. 1 1
      src/PixiEditor.SVG/Units/SvgLinkUnit.cs
  24. 18 1
      src/PixiEditor.SVG/Units/SvgNumericUnit.cs
  25. 49 0
      src/PixiEditor.SVG/Units/SvgPaintServerUnit.cs
  26. 2 1
      src/PixiEditor.SVG/Units/SvgRectUnit.cs
  27. 4 2
      src/PixiEditor.SVG/Units/SvgStringUnit.cs
  28. 6 4
      src/PixiEditor.SVG/Units/SvgStyleUnit.cs
  29. 3 2
      src/PixiEditor.SVG/Units/SvgTransformUnit.cs
  30. 4 2
      src/PixiEditor.SVG/Units/SvgUnit.cs
  31. 9 0
      src/PixiEditor/Helpers/Extensions/ColorHelpers.cs
  32. 6 4
      src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs
  33. 3 17
      src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs
  34. 1 1
      src/colorpicker

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit d7395e76746a549d8aa6132aee598fa1b973db9a
+Subproject commit 411b72fe4a39f0c9dd6f8ea61dbad7e6875f60b6

+ 48 - 0
src/PixiEditor.SVG/Elements/SvgDefs.cs

@@ -0,0 +1,48 @@
+using System.Xml;
+using PixiEditor.SVG.Features;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgDefs() : SvgElement("defs"), IElementContainer
+{
+    public List<SvgElement> Children { get; } = new();
+
+    public bool TryFindElement(string id, out SvgElement element)
+    {
+        return TryFindElement(Children, id, out element);
+    }
+
+    public override void ParseData(XmlReader reader, SvgDefs defs)
+    {
+
+    }
+
+    private bool TryFindElement(List<SvgElement> root, string id, out SvgElement? element)
+    {
+        if (root == null || root.Count == 0)
+        {
+            element = null;
+            return false;
+        }
+
+        foreach (SvgElement child in root)
+        {
+            if (child.Id.Unit?.Value == id)
+            {
+                element = child;
+                return true;
+            }
+
+            if (child is IElementContainer container && container.Children.Count != 0)
+            {
+                if (TryFindElement(container.Children, id, out element))
+                {
+                    return true;
+                }
+            }
+        }
+
+        element = null;
+        return false;
+    }
+}

+ 17 - 7
src/PixiEditor.SVG/Elements/SvgGroup.cs

@@ -5,22 +5,32 @@ using PixiEditor.SVG.Units;
 
 
 namespace PixiEditor.SVG.Elements;
 namespace PixiEditor.SVG.Elements;
 
 
-public class SvgGroup() : SvgElement("g"), ITransformable, IFillable, IStrokable, IOpacity, IElementContainer
+public class SvgGroup()
+    : SvgElement("g"), ITransformable, IFillable, IStrokable, IOpacity, IElementContainer, IDefsStorage
 {
 {
     public List<SvgElement> Children { get; } = new();
     public List<SvgElement> Children { get; } = new();
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
-    public SvgProperty<SvgColorUnit> Fill { get; } = new("fill");
+    public SvgProperty<SvgPaintServerUnit> Fill { get; } = new("fill");
     public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
     public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
-    public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; } = new("stroke-linecap");
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; } = new("stroke-linecap");
     public SvgProperty<SvgEnumUnit<SvgStrokeLineJoin>> StrokeLineJoin { get; } = new("stroke-linejoin");
     public SvgProperty<SvgEnumUnit<SvgStrokeLineJoin>> StrokeLineJoin { get; } = new("stroke-linejoin");
     public SvgProperty<SvgNumericUnit> Opacity { get; } = new("opacity");
     public SvgProperty<SvgNumericUnit> Opacity { get; } = new("opacity");
+    public SvgDefs Defs { get; } = new();
 
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
     {
-        List<SvgProperty> properties = new List<SvgProperty>() { Transform, Fill, Stroke, StrokeWidth, StrokeLineCap, StrokeLineJoin };
-        ParseAttributes(properties, reader);
-    }
+        List<SvgProperty> properties = new List<SvgProperty>()
+        {
+            Transform,
+            Fill,
+            Stroke,
+            StrokeWidth,
+            StrokeLineCap,
+            StrokeLineJoin
+        };
 
 
+        ParseAttributes(properties, reader, defs); // TODO: merge with Defs?
+    }
 }
 }

+ 2 - 2
src/PixiEditor.SVG/Elements/SvgImage.cs

@@ -21,9 +21,9 @@ public class SvgImage : SvgElement
         RequiredNamespaces.Add("xlink", "http://www.w3.org/1999/xlink");
         RequiredNamespaces.Add("xlink", "http://www.w3.org/1999/xlink");
     }
     }
 
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
     {
         List<SvgProperty> properties = new List<SvgProperty>() { X, Y, Width, Height, Href, Mask, ImageRendering };
         List<SvgProperty> properties = new List<SvgProperty>() { X, Y, Width, Height, Href, Mask, ImageRendering };
-        ParseAttributes(properties, reader);
+        ParseAttributes(properties, reader, defs);
     }
     }
 }
 }

+ 71 - 0
src/PixiEditor.SVG/Elements/SvgLinearGradient.cs

@@ -0,0 +1,71 @@
+using System.Xml;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
+using PixiEditor.SVG.Enums;
+using PixiEditor.SVG.Features;
+using PixiEditor.SVG.Units;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgLinearGradient() : SvgElement("linearGradient"), IElementContainer, IPaintServer
+{
+    public List<SvgElement> Children { get; } = new();
+    public SvgProperty<SvgTransformUnit> GradientTransform { get; } = new("gradientTransform");
+    public SvgProperty<SvgNumericUnit> X1 { get; } = new("x1");
+    public SvgProperty<SvgNumericUnit> Y1 { get; } = new("y1");
+    public SvgProperty<SvgNumericUnit> X2 { get; } = new("x2");
+    public SvgProperty<SvgNumericUnit> Y2 { get; } = new("y2");
+    public SvgProperty<SvgEnumUnit<SvgSpreadMethod>> SpreadMethod { get; } = new("spreadMethod");
+    public SvgProperty<SvgEnumUnit<SvgGradientUnit>> GradientUnits { get; } = new("gradientUnits");
+
+    public override void ParseData(XmlReader reader, SvgDefs defs)
+    {
+        List<SvgProperty> properties = GetProperties().ToList();
+
+        do
+        {
+            ParseAttributes(properties, reader, defs);
+        } while (reader.MoveToNextAttribute());
+    }
+
+    protected IEnumerable<SvgProperty> GetProperties()
+    {
+        yield return GradientTransform;
+        yield return X1;
+        yield return Y1;
+        yield return X2;
+        yield return Y2;
+        yield return SpreadMethod;
+        yield return GradientUnits;
+    }
+
+    public Paintable GetPaintable()
+    {
+        VecD start = new VecD(GetUnit(X1)?.Value ?? 0, GetUnit(Y1)?.Value ?? 0.5);
+        VecD end = new VecD(GetUnit(X2)?.Value ?? 1, GetUnit(Y2)?.Value ?? 0.5);
+
+        List<GradientStop> gradientStops = new();
+        foreach (SvgElement child in Children)
+        {
+            if (child is SvgStop stop)
+            {
+                Color color = stop.GetUnit(stop.StopColor)?.Color ?? Colors.Black;
+                color = color.WithAlpha((byte)((stop.StopOpacity.Unit?.NormalizedValue() ?? 1) * 255));
+                gradientStops.Add(
+                    new GradientStop(color, GetUnit(stop.Offset)?.NormalizedValue() ?? 0));
+            }
+        }
+
+        var unit = GetUnit(GradientUnits)?.Value ?? SvgGradientUnit.ObjectBoundingBox;
+        var transform = GetUnit(GradientTransform)?.MatrixValue ?? Matrix3X3.Identity;
+
+        // TODO: Implement gradient transform, spread method and gradient units
+        return new LinearGradientPaintable(start, end, gradientStops)
+        {
+            AbsoluteValues = unit == SvgGradientUnit.UserSpaceOnUse,
+            Transform = transform
+        };
+    }
+}

+ 2 - 2
src/PixiEditor.SVG/Elements/SvgMask.cs

@@ -13,9 +13,9 @@ public class SvgMask() : SvgElement("mask"), IElementContainer
     public SvgProperty<SvgNumericUnit> Height { get; } = new("height");
     public SvgProperty<SvgNumericUnit> Height { get; } = new("height");
     public List<SvgElement> Children { get; } = new();
     public List<SvgElement> Children { get; } = new();
 
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
     {
         List<SvgProperty> properties = new List<SvgProperty>() { X, Y, Width, Height };
         List<SvgProperty> properties = new List<SvgProperty>() { X, Y, Width, Height };
-        ParseAttributes(properties, reader);
+        ParseAttributes(properties, reader, defs);
     }
     }
 }
 }

+ 4 - 4
src/PixiEditor.SVG/Elements/SvgPrimitive.cs

@@ -8,9 +8,9 @@ namespace PixiEditor.SVG.Elements;
 public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITransformable, IFillable, IStrokable, IOpacity
 public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITransformable, IFillable, IStrokable, IOpacity
 {
 {
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
-    public SvgProperty<SvgColorUnit> Fill { get; } = new("fill");
+    public SvgProperty<SvgPaintServerUnit> Fill { get; } = new("fill");
     public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
     public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
-    public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
     
     
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; } = new("stroke-linecap");
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; } = new("stroke-linecap");
@@ -19,7 +19,7 @@ public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITrans
 
 
     public SvgProperty<SvgNumericUnit> Opacity { get; } = new("opacity");
     public SvgProperty<SvgNumericUnit> Opacity { get; } = new("opacity");
 
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
     {
         List<SvgProperty> properties = GetProperties().ToList();
         List<SvgProperty> properties = GetProperties().ToList();
         
         
@@ -34,7 +34,7 @@ public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITrans
 
 
         do
         do
         {
         {
-            ParseAttributes(properties, reader);
+            ParseAttributes(properties, reader, defs);
         } while (reader.MoveToNextAttribute());
         } while (reader.MoveToNextAttribute());
     }
     }
 
 

+ 76 - 0
src/PixiEditor.SVG/Elements/SvgRadialGradient.cs

@@ -0,0 +1,76 @@
+using System.Xml;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
+using PixiEditor.SVG.Enums;
+using PixiEditor.SVG.Features;
+using PixiEditor.SVG.Units;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgRadialGradient() : SvgElement("radialGradient"), IElementContainer, IPaintServer
+{
+    public List<SvgElement> Children { get; } = new();
+    public SvgProperty<SvgTransformUnit> GradientTransform { get; } = new("gradientTransform");
+    public SvgProperty<SvgNumericUnit> Cx { get; } = new("cx");
+    public SvgProperty<SvgNumericUnit> Cy { get; } = new("cy");
+    public SvgProperty<SvgNumericUnit> R { get; } = new("r");
+    public SvgProperty<SvgNumericUnit> Fx { get; } = new("fx");
+    public SvgProperty<SvgNumericUnit> Fy { get; } = new("fy");
+    public SvgProperty<SvgEnumUnit<SvgSpreadMethod>> SpreadMethod { get; } = new("spreadMethod");
+    public SvgProperty<SvgEnumUnit<SvgGradientUnit>> GradientUnits { get; } = new("gradientUnits");
+
+    public override void ParseData(XmlReader reader, SvgDefs defs)
+    {
+        List<SvgProperty> properties = GetProperties().ToList();
+
+        do
+        {
+            ParseAttributes(properties, reader, defs);
+        } while (reader.MoveToNextAttribute());
+    }
+
+    protected IEnumerable<SvgProperty> GetProperties()
+    {
+        yield return GradientTransform;
+        yield return Cx;
+        yield return Cy;
+        yield return R;
+        yield return Fx;
+        yield return Fy;
+        yield return SpreadMethod;
+        yield return GradientUnits;
+    }
+
+    public Paintable GetPaintable()
+    {
+        VecD center = new VecD(GetUnit(Cx)?.Value ?? 0.5, GetUnit(Cy)?.Value ?? 0.5);
+        //VecD focus = new VecD(Fx.Unit.Value.PixelsValue ?? 0, Fy.Unit.Value.PixelsValue ?? 0);
+        double radius = GetUnit(R)?.Value ?? 0.5;
+
+        List<GradientStop> gradientStops = new();
+        foreach (SvgElement child in Children)
+        {
+            if (child is SvgStop stop)
+            {
+                Color color = stop.GetUnit(stop.StopColor)?.Color ?? Colors.Black;
+                color = color.WithAlpha((byte)((stop.StopOpacity.Unit?.NormalizedValue() ?? 1) * 255));
+                gradientStops.Add(
+                    new GradientStop(color, stop.GetUnit(stop.Offset)?.NormalizedValue() ?? 0));
+            }
+        }
+
+        var unit = GetUnit(GradientUnits)?.Value ?? SvgGradientUnit.ObjectBoundingBox;
+        var transform = GetUnit(GradientTransform)?.MatrixValue ?? Matrix3X3.Identity;
+
+        RadialGradientPaintable radialGradientPaintable =
+            new(center, radius, gradientStops)
+            {
+                AbsoluteValues = unit == SvgGradientUnit.UserSpaceOnUse,
+                Transform = transform
+            };
+
+        return radialGradientPaintable;
+    }
+}

+ 28 - 0
src/PixiEditor.SVG/Elements/SvgStop.cs

@@ -0,0 +1,28 @@
+using System.Xml;
+using PixiEditor.SVG.Units;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgStop() : SvgElement("stop")
+{
+    public SvgProperty<SvgColorUnit> StopColor { get; } = new("stop-color");
+    public SvgProperty<SvgNumericUnit> Offset { get; } = new("offset");
+    public SvgProperty<SvgNumericUnit> StopOpacity { get; } = new("stop-opacity");
+
+    public override void ParseData(XmlReader reader, SvgDefs defs)
+    {
+        List<SvgProperty> properties = GetProperties().ToList();
+
+        do
+        {
+            ParseAttributes(properties, reader, defs);
+        } while (reader.MoveToNextAttribute());
+    }
+
+    private IEnumerable<SvgProperty> GetProperties()
+    {
+        yield return StopColor;
+        yield return Offset;
+        yield return StopOpacity;
+    }
+}

+ 2 - 2
src/PixiEditor.SVG/Elements/SvgText.cs

@@ -14,9 +14,9 @@ public class SvgText() : SvgPrimitive("text")
     public SvgProperty<SvgEnumUnit<SvgFontWeight>> FontWeight { get; } = new("font-weight");
     public SvgProperty<SvgEnumUnit<SvgFontWeight>> FontWeight { get; } = new("font-weight");
     public SvgProperty<SvgEnumUnit<SvgFontStyle>> FontStyle { get; } = new("font-style");
     public SvgProperty<SvgEnumUnit<SvgFontStyle>> FontStyle { get; } = new("font-style");
 
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
     {
-        base.ParseData(reader);
+        base.ParseData(reader, defs);
         Text.Unit = new SvgStringUnit(ParseContent(reader));
         Text.Unit = new SvgStringUnit(ParseContent(reader));
     }
     }
 
 

+ 7 - 0
src/PixiEditor.SVG/Enums/SvgGradientUnit.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.SVG.Enums;
+
+public enum SvgGradientUnit
+{
+    UserSpaceOnUse,
+    ObjectBoundingBox
+}

+ 8 - 0
src/PixiEditor.SVG/Enums/SvgSpreadMethod.cs

@@ -0,0 +1,8 @@
+namespace PixiEditor.SVG.Enums;
+
+public enum SvgSpreadMethod
+{
+    Pad,
+    Reflect,
+    Repeat
+}

+ 8 - 0
src/PixiEditor.SVG/Features/IDefsStorage.cs

@@ -0,0 +1,8 @@
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG.Features;
+
+public interface IDefsStorage
+{
+    public SvgDefs Defs { get; }
+}

+ 1 - 1
src/PixiEditor.SVG/Features/IFillable.cs

@@ -5,6 +5,6 @@ namespace PixiEditor.SVG.Features;
 
 
 public interface IFillable
 public interface IFillable
 {
 {
-    public SvgProperty<SvgColorUnit> Fill { get; }
+    public SvgProperty<SvgPaintServerUnit> Fill { get; }
     public SvgProperty<SvgNumericUnit> FillOpacity { get; }
     public SvgProperty<SvgNumericUnit> FillOpacity { get; }
 }
 }

+ 8 - 0
src/PixiEditor.SVG/Features/IPaintServer.cs

@@ -0,0 +1,8 @@
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+
+namespace PixiEditor.SVG.Features;
+
+public interface IPaintServer
+{
+    public Paintable GetPaintable();
+}

+ 1 - 1
src/PixiEditor.SVG/Features/IStrokable.cs

@@ -5,7 +5,7 @@ namespace PixiEditor.SVG.Features;
 
 
 public interface IStrokable
 public interface IStrokable
 {
 {
-    public SvgProperty<SvgColorUnit> Stroke { get; }
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; }
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; }
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineJoin>> StrokeLineJoin { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineJoin>> StrokeLineJoin { get; }

+ 15 - 8
src/PixiEditor.SVG/StyleContext.cs

@@ -1,5 +1,7 @@
-using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Enums;
 using PixiEditor.SVG.Enums;
 using PixiEditor.SVG.Features;
 using PixiEditor.SVG.Features;
 using PixiEditor.SVG.Units;
 using PixiEditor.SVG.Units;
@@ -9,8 +11,8 @@ namespace PixiEditor.SVG;
 public struct StyleContext
 public struct StyleContext
 {
 {
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; }
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; }
-    public SvgProperty<SvgColorUnit> Stroke { get; }
-    public SvgProperty<SvgColorUnit> Fill { get; }
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; }
+    public SvgProperty<SvgPaintServerUnit> Fill { get; }
     public SvgProperty<SvgNumericUnit> FillOpacity { get; }
     public SvgProperty<SvgNumericUnit> FillOpacity { get; }
     public SvgProperty<SvgTransformUnit> Transform { get; }
     public SvgProperty<SvgTransformUnit> Transform { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; }
@@ -18,6 +20,7 @@ public struct StyleContext
     public SvgProperty<SvgNumericUnit> Opacity { get; }
     public SvgProperty<SvgNumericUnit> Opacity { get; }
     public SvgProperty<SvgStyleUnit> InlineStyle { get; set; }
     public SvgProperty<SvgStyleUnit> InlineStyle { get; set; }
     public VecD ViewboxOrigin { get; set; }
     public VecD ViewboxOrigin { get; set; }
+    public SvgDefs Defs { get; set; }
 
 
     public StyleContext()
     public StyleContext()
     {
     {
@@ -25,19 +28,20 @@ public struct StyleContext
         Stroke = new("stroke");
         Stroke = new("stroke");
         Fill = new("fill");
         Fill = new("fill");
         FillOpacity = new("fill-opacity");
         FillOpacity = new("fill-opacity");
-        Fill.Unit = new SvgColorUnit?(new SvgColorUnit("black"));
+        Fill.Unit = SvgPaintServerUnit.FromColor(Colors.Black);
         Transform = new("transform");
         Transform = new("transform");
         StrokeLineCap = new("stroke-linecap");
         StrokeLineCap = new("stroke-linecap");
         StrokeLineJoin = new("stroke-linejoin");
         StrokeLineJoin = new("stroke-linejoin");
         Opacity = new("opacity");
         Opacity = new("opacity");
         InlineStyle = new("style");
         InlineStyle = new("style");
+        Defs = new();
     }
     }
 
 
     public StyleContext(SvgDocument document)
     public StyleContext(SvgDocument document)
     {
     {
         StrokeWidth = FallbackToCssStyle(document.StrokeWidth, document.Style);
         StrokeWidth = FallbackToCssStyle(document.StrokeWidth, document.Style);
         Stroke = FallbackToCssStyle(document.Stroke, document.Style);
         Stroke = FallbackToCssStyle(document.Stroke, document.Style);
-        Fill = FallbackToCssStyle(document.Fill, document.Style, new SvgColorUnit("black"));
+        Fill = FallbackToCssStyle(document.Fill, document.Style, SvgPaintServerUnit.FromColor(Colors.Black));
         FillOpacity = FallbackToCssStyle(document.FillOpacity, document.Style);
         FillOpacity = FallbackToCssStyle(document.FillOpacity, document.Style);
         Transform = FallbackToCssStyle(document.Transform, document.Style, new SvgTransformUnit(Matrix3X3.Identity));
         Transform = FallbackToCssStyle(document.Transform, document.Style, new SvgTransformUnit(Matrix3X3.Identity));
         StrokeLineCap = FallbackToCssStyle(document.StrokeLineCap, document.Style);
         StrokeLineCap = FallbackToCssStyle(document.StrokeLineCap, document.Style);
@@ -47,6 +51,7 @@ public struct StyleContext
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.X : 0,
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.X : 0,
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.Y : 0);
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.Y : 0);
         InlineStyle = document.Style;
         InlineStyle = document.Style;
+        Defs = document.Defs;
     }
     }
 
 
     public StyleContext WithElement(SvgElement element)
     public StyleContext WithElement(SvgElement element)
@@ -76,7 +81,7 @@ public struct StyleContext
         if (element is IFillable fillableElement)
         if (element is IFillable fillableElement)
         {
         {
             styleContext.Fill.Unit = FallbackToCssStyle(fillableElement.Fill, styleContext.Fill,
             styleContext.Fill.Unit = FallbackToCssStyle(fillableElement.Fill, styleContext.Fill,
-                styleContext.InlineStyle, new SvgColorUnit("black")).Unit;
+                styleContext.InlineStyle, SvgPaintServerUnit.FromColor(Colors.Black)).Unit;
             styleContext.FillOpacity.Unit =
             styleContext.FillOpacity.Unit =
                 FallbackToCssStyle(fillableElement.FillOpacity, styleContext.FillOpacity, styleContext.InlineStyle)
                 FallbackToCssStyle(fillableElement.FillOpacity, styleContext.FillOpacity, styleContext.InlineStyle)
                     .Unit;
                     .Unit;
@@ -160,6 +165,8 @@ public struct StyleContext
             styleContext.InlineStyle.Unit = InlineStyle.Unit;
             styleContext.InlineStyle.Unit = InlineStyle.Unit;
         }
         }
 
 
+        styleContext.Defs = Defs;
+
         return styleContext;
         return styleContext;
     }
     }
 
 
@@ -174,7 +181,7 @@ public struct StyleContext
         }
         }
 
 
         SvgStyleUnit? style = inlineStyle.Unit;
         SvgStyleUnit? style = inlineStyle.Unit;
-        return style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName)
+        return style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName, Defs)
                ?? (fallback.HasValue
                ?? (fallback.HasValue
                    ? new SvgProperty<TUnit>(property.SvgName) { Unit = fallback.Value }
                    ? new SvgProperty<TUnit>(property.SvgName) { Unit = fallback.Value }
                    : new SvgProperty<TUnit>(property.SvgName));
                    : new SvgProperty<TUnit>(property.SvgName));
@@ -191,7 +198,7 @@ public struct StyleContext
         }
         }
 
 
         SvgStyleUnit? style = inlineStyle.Unit;
         SvgStyleUnit? style = inlineStyle.Unit;
-        var styleProp = style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName);
+        var styleProp = style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName, Defs);
         if (styleProp != null) return styleProp;
         if (styleProp != null) return styleProp;
         if(parentStyleProperty.Unit != null)
         if(parentStyleProperty.Unit != null)
         {
         {

+ 9 - 6
src/PixiEditor.SVG/SvgDocument.cs

@@ -1,20 +1,21 @@
 using System.Xml;
 using System.Xml;
 using System.Xml.Linq;
 using System.Xml.Linq;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Enums;
 using PixiEditor.SVG.Enums;
 using PixiEditor.SVG.Features;
 using PixiEditor.SVG.Features;
 using PixiEditor.SVG.Units;
 using PixiEditor.SVG.Units;
 
 
 namespace PixiEditor.SVG;
 namespace PixiEditor.SVG;
 
 
-public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFillable, IStrokable, IOpacity
+public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFillable, IStrokable, IOpacity, IDefsStorage
 {
 {
     public string RootNamespace { get; set; } = "http://www.w3.org/2000/svg";
     public string RootNamespace { get; set; } = "http://www.w3.org/2000/svg";
     public string Version { get; set; } = "1.1";
     public string Version { get; set; } = "1.1";
 
 
     public SvgProperty<SvgRectUnit> ViewBox { get; } = new("viewBox");
     public SvgProperty<SvgRectUnit> ViewBox { get; } = new("viewBox");
-    public SvgProperty<SvgColorUnit> Fill { get; } = new("fill");
-    public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
+    public SvgProperty<SvgPaintServerUnit> Fill { get; } = new("fill");
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
 
 
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; } = new("stroke-linecap");
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; } = new("stroke-linecap");
@@ -23,6 +24,8 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
     public SvgProperty<SvgNumericUnit> Opacity { get; } = new("opacity");
     public SvgProperty<SvgNumericUnit> Opacity { get; } = new("opacity");
     public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
     public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
+
+    public SvgDefs Defs { get; set; } = new();
     public List<SvgElement> Children { get; } = new();
     public List<SvgElement> Children { get; } = new();
 
 
     public SvgDocument() : base("svg")
     public SvgDocument() : base("svg")
@@ -34,7 +37,7 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
         ViewBox.Unit = new SvgRectUnit(viewBox);
         ViewBox.Unit = new SvgRectUnit(viewBox);
     }
     }
 
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
     {
         List<SvgProperty> properties = new()
         List<SvgProperty> properties = new()
         {
         {
@@ -46,10 +49,10 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
             ViewBox,
             ViewBox,
             StrokeLineCap,
             StrokeLineCap,
             StrokeLineJoin,
             StrokeLineJoin,
-            Opacity
+            Opacity,
         };
         };
 
 
-        ParseAttributes(properties, reader);
+        ParseAttributes(properties, reader, defs); // TODO: merge with Defs?
     }
     }
 
 
     public string ToXml()
     public string ToXml()

+ 33 - 8
src/PixiEditor.SVG/SvgElement.cs

@@ -1,6 +1,7 @@
 using System.Text;
 using System.Text;
 using System.Xml;
 using System.Xml;
 using System.Xml.Linq;
 using System.Xml.Linq;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Exceptions;
 using PixiEditor.SVG.Exceptions;
 using PixiEditor.SVG.Features;
 using PixiEditor.SVG.Features;
 using PixiEditor.SVG.Units;
 using PixiEditor.SVG.Units;
@@ -57,13 +58,37 @@ public class SvgElement(string tagName)
         return element;
         return element;
     }
     }
 
 
-    public virtual void ParseData(XmlReader reader)
+    public virtual void ParseData(XmlReader reader, SvgDefs defs)
     {
     {
         // This is supposed to be overriden by child classes
         // This is supposed to be overriden by child classes
         throw new SvgParsingException($"Element {TagName} does not support parsing");
         throw new SvgParsingException($"Element {TagName} does not support parsing");
     }
     }
 
 
-    protected void ParseAttributes(List<SvgProperty> properties, XmlReader reader)
+    /// <summary>
+    /// Gets unit for property. If property does not have unit, it will try to get it from inlined style.
+    /// </summary>
+    /// <param name="forProperty">Property to get unit for</param>
+    /// <param name="defs">Optional defs element to get units from</param>
+    /// <typeparam name="TUnit">Type of unit to get</typeparam>
+    /// <returns>Unit for property</returns>
+    public TUnit? GetUnit<TUnit>(SvgProperty<TUnit> forProperty, SvgDefs defs = default)
+        where TUnit : struct, ISvgUnit
+    {
+        if (forProperty.Unit != null) return forProperty.Unit.Value;
+
+        if (Style.Unit != null)
+        {
+            var styleProp = Style.Unit.Value.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(forProperty.SvgName, defs);
+            if (styleProp != null && styleProp.Unit != null)
+            {
+                return styleProp.Unit.Value;
+            }
+        }
+
+        return null;
+    }
+
+    protected void ParseAttributes(List<SvgProperty> properties, XmlReader reader, SvgDefs defs)
     {
     {
         if (!properties.Contains(Id))
         if (!properties.Contains(Id))
         {
         {
@@ -81,27 +106,27 @@ public class SvgElement(string tagName)
                 string.Equals(x.SvgName, reader.Name, StringComparison.OrdinalIgnoreCase));
                 string.Equals(x.SvgName, reader.Name, StringComparison.OrdinalIgnoreCase));
             if (matchingProperty != null)
             if (matchingProperty != null)
             {
             {
-                ParseAttribute(matchingProperty, reader);
+                ParseAttribute(matchingProperty, reader, defs);
             }
             }
         } while (reader.MoveToNextAttribute());
         } while (reader.MoveToNextAttribute());
     }
     }
 
 
-    private void ParseAttribute(SvgProperty property, XmlReader reader)
+    private void ParseAttribute(SvgProperty property, XmlReader reader, SvgDefs defs)
     {
     {
         if (property is SvgList list)
         if (property is SvgList list)
         {
         {
-            ParseListProperty(list, reader);
+            ParseListProperty(list, reader, defs);
         }
         }
         else
         else
         {
         {
             property.Unit ??= property.CreateDefaultUnit();
             property.Unit ??= property.CreateDefaultUnit();
-            property.Unit.ValuesFromXml(reader.Value);
+            property.Unit.ValuesFromXml(reader.Value, defs);
         }
         }
     }
     }
 
 
-    private void ParseListProperty(SvgList list, XmlReader reader)
+    private void ParseListProperty(SvgList list, XmlReader reader, SvgDefs defs)
     {
     {
         list.Unit ??= list.CreateDefaultUnit();
         list.Unit ??= list.CreateDefaultUnit();
-        list.Unit.ValuesFromXml(reader.Value);
+        list.Unit.ValuesFromXml(reader.Value, defs);
     }
     }
 }
 }

+ 41 - 13
src/PixiEditor.SVG/SvgParser.cs

@@ -21,7 +21,11 @@ public class SvgParser
         { "mask", typeof(SvgMask) },
         { "mask", typeof(SvgMask) },
         { "image", typeof(SvgImage) },
         { "image", typeof(SvgImage) },
         { "svg", typeof(SvgDocument) },
         { "svg", typeof(SvgDocument) },
-        { "text", typeof(SvgText) }
+        { "text", typeof(SvgText) },
+        { "linearGradient", typeof(SvgLinearGradient) },
+        { "radialGradient", typeof(SvgRadialGradient) },
+        { "stop", typeof(SvgStop) },
+        { "defs", typeof(SvgDefs) }
     };
     };
 
 
     public string Source { get; set; }
     public string Source { get; set; }
@@ -41,25 +45,28 @@ public class SvgParser
         {
         {
             return null;
             return null;
         }
         }
-        
-        SvgDocument root = (SvgDocument)ParseElement(reader)!;
+
+        SvgDocument root = (SvgDocument)ParseElement(reader, new SvgDefs())!;
 
 
         RectD bounds = ParseBounds(reader); // this takes into account viewBox, width, height, x, y
         RectD bounds = ParseBounds(reader); // this takes into account viewBox, width, height, x, y
-        
+
         root.ViewBox.Unit = new SvgRectUnit(bounds);
         root.ViewBox.Unit = new SvgRectUnit(bounds);
 
 
+        using var defsReader = document.CreateReader();
+        root.Defs = ParseDefs(defsReader);
+
         while (reader.Read())
         while (reader.Read())
         {
         {
             if (reader.NodeType == XmlNodeType.Element)
             if (reader.NodeType == XmlNodeType.Element)
             {
             {
-                SvgElement? element = ParseElement(reader);
+                SvgElement? element = ParseElement(reader, root.Defs);
                 if (element != null)
                 if (element != null)
                 {
                 {
                     root.Children.Add(element);
                     root.Children.Add(element);
 
 
-                    if (element is IElementContainer container)
+                    if (element is IElementContainer container && element.TagName != "defs")
                     {
                     {
-                        ParseChildren(reader, container, element.TagName);
+                        ParseChildren(reader, container, root.Defs, element.TagName);
                     }
                     }
                 }
                 }
             }
             }
@@ -68,38 +75,59 @@ public class SvgParser
         return root;
         return root;
     }
     }
 
 
-    private void ParseChildren(XmlReader reader, IElementContainer container, string tagName)
+    private SvgDefs ParseDefs(XmlReader reader)
+    {
+        XmlNodeType node = reader.MoveToContent();
+        if (node != XmlNodeType.Element)
+        {
+            return null;
+        }
+
+        while (reader.Read())
+        {
+            if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "defs")
+            {
+                break;
+            }
+        }
+
+        SvgDefs defs = new();
+        ParseChildren(reader, defs, new SvgDefs(), "defs");
+        return defs;
+    }
+
+    private void ParseChildren(XmlReader reader, IElementContainer container, SvgDefs defs, string tagName)
     {
     {
         while (reader.Read())
         while (reader.Read())
         {
         {
             if (reader.NodeType == XmlNodeType.Element)
             if (reader.NodeType == XmlNodeType.Element)
             {
             {
-                SvgElement? element = ParseElement(reader);
+                SvgElement? element = ParseElement(reader, defs);
                 if (element != null)
                 if (element != null)
                 {
                 {
                     container.Children.Add(element);
                     container.Children.Add(element);
 
 
                     if (element is IElementContainer childContainer)
                     if (element is IElementContainer childContainer)
                     {
                     {
-                        ParseChildren(reader, childContainer, element.TagName);
+                        ParseChildren(reader, childContainer, defs, element.TagName);
                     }
                     }
                 }
                 }
             }
             }
-            else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == tagName)
+            else if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == tagName)
             {
             {
                 break;
                 break;
             }
             }
         }
         }
     }
     }
 
 
-    private SvgElement? ParseElement(XmlReader reader)
+    private SvgElement? ParseElement(XmlReader reader, SvgDefs defs)
     {
     {
         if (wellKnownElements.TryGetValue(reader.LocalName, out Type elementType))
         if (wellKnownElements.TryGetValue(reader.LocalName, out Type elementType))
         {
         {
             SvgElement element = (SvgElement)Activator.CreateInstance(elementType);
             SvgElement element = (SvgElement)Activator.CreateInstance(elementType);
             if (reader.MoveToFirstAttribute())
             if (reader.MoveToFirstAttribute())
             {
             {
-                element.ParseData(reader);
+                element.ParseData(reader, defs);
             }
             }
 
 
             return element;
             return element;

+ 2 - 1
src/PixiEditor.SVG/Units/SvgColorUnit.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Exceptions;
 using PixiEditor.SVG.Exceptions;
 using PixiEditor.SVG.Utils;
 using PixiEditor.SVG.Utils;
 
 
@@ -57,7 +58,7 @@ public struct SvgColorUnit : ISvgUnit
         return Value;
         return Value;
     }
     }
 
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
     {
         Value = readerValue;
         Value = readerValue;
     }
     }

+ 2 - 1
src/PixiEditor.SVG/Units/SvgEnumUnit.cs

@@ -1,5 +1,6 @@
 using System.Reflection;
 using System.Reflection;
 using PixiEditor.SVG.Attributes;
 using PixiEditor.SVG.Attributes;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Helpers;
 using PixiEditor.SVG.Helpers;
 
 
 namespace PixiEditor.SVG.Units;
 namespace PixiEditor.SVG.Units;
@@ -26,7 +27,7 @@ public struct SvgEnumUnit<T> : ISvgUnit where T : struct, Enum
         return Value.ToString().ToKebabCase();
         return Value.ToString().ToKebabCase();
     }
     }
 
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
     {
         bool matched = TryMatchEnum(readerValue);
         bool matched = TryMatchEnum(readerValue);
         if (!matched && Enum.TryParse(readerValue.FromKebabToTitleCase(), out T result))
         if (!matched && Enum.TryParse(readerValue.FromKebabToTitleCase(), out T result))

+ 1 - 1
src/PixiEditor.SVG/Units/SvgLinkUnit.cs

@@ -10,7 +10,7 @@ public struct SvgLinkUnit : ISvgUnit
         return ObjectReference != null ? $"url(#{ObjectReference}" : string.Empty;
         return ObjectReference != null ? $"url(#{ObjectReference}" : string.Empty;
     }
     }
 
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
     {
         if (readerValue.StartsWith("url(#") && readerValue.EndsWith(')'))
         if (readerValue.StartsWith("url(#") && readerValue.EndsWith(')'))
         {
         {

+ 18 - 1
src/PixiEditor.SVG/Units/SvgNumericUnit.cs

@@ -1,4 +1,5 @@
 using System.Globalization;
 using System.Globalization;
+using PixiEditor.SVG.Elements;
 
 
 namespace PixiEditor.SVG.Units;
 namespace PixiEditor.SVG.Units;
 
 
@@ -62,7 +63,7 @@ public struct SvgNumericUnit(double value, string postFix) : ISvgUnit
         return $"{invariantValue}{PostFix}";
         return $"{invariantValue}{PostFix}";
     }
     }
 
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
     {
         string? extractedPostFix = ExtractPostFix(readerValue);
         string? extractedPostFix = ExtractPostFix(readerValue);
 
 
@@ -111,4 +112,20 @@ public struct SvgNumericUnit(double value, string postFix) : ISvgUnit
 
 
         return readerValue.Substring(postFixStartIndex);
         return readerValue.Substring(postFixStartIndex);
     }
     }
+
+    public double NormalizedValue(bool clamp = true)
+    {
+        double value = Value;
+        if (PostFix == "%")
+        {
+            value /= 100;
+        }
+
+        if (clamp)
+        {
+            value = Math.Clamp(value, 0, 1);
+        }
+
+        return value;
+    }
 }
 }

+ 49 - 0
src/PixiEditor.SVG/Units/SvgPaintServerUnit.cs

@@ -0,0 +1,49 @@
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using PixiEditor.SVG.Elements;
+using PixiEditor.SVG.Features;
+
+namespace PixiEditor.SVG.Units;
+
+public struct SvgPaintServerUnit : ISvgUnit
+{
+    public Paintable Paintable { get; set; }
+
+    public SvgLinkUnit? LinksTo { get; set; } = null;
+
+    public SvgPaintServerUnit(Paintable paintable)
+    {
+        Paintable = paintable;
+    }
+
+    public static SvgPaintServerUnit FromColor(Color color)
+    {
+        return new SvgPaintServerUnit(new ColorPaintable(color));
+    }
+
+    public string ToXml()
+    {
+        throw new NotImplementedException();
+    }
+
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
+    {
+        var linkUnit = new SvgLinkUnit();
+        linkUnit.ValuesFromXml(readerValue, defs);
+        LinksTo = linkUnit;
+        if (string.IsNullOrEmpty(LinksTo.Value.ObjectReference))
+        {
+            LinksTo = null;
+            SvgColorUnit colorUnit = new SvgColorUnit();
+            colorUnit.ValuesFromXml(readerValue, defs);
+            Paintable = new ColorPaintable(colorUnit.Color);
+        }
+        else
+        {
+            if(defs.TryFindElement(LinksTo.Value.ObjectReference, out SvgElement? element) && element is IPaintServer server)
+            {
+                Paintable = server.GetPaintable();
+            }
+        }
+    }
+}

+ 2 - 1
src/PixiEditor.SVG/Units/SvgRectUnit.cs

@@ -1,4 +1,5 @@
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.SVG.Elements;
 
 
 namespace PixiEditor.SVG.Units;
 namespace PixiEditor.SVG.Units;
 
 
@@ -10,7 +11,7 @@ public struct SvgRectUnit(RectD rect) : ISvgUnit
         return $"{Value.X} {Value.Y} {Value.Width} {Value.Height}";
         return $"{Value.X} {Value.Y} {Value.Width} {Value.Height}";
     }
     }
 
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
     {
         string[] values = readerValue.Split(' ');
         string[] values = readerValue.Split(' ');
         
         

+ 4 - 2
src/PixiEditor.SVG/Units/SvgStringUnit.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.SVG.Units;
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG.Units;
 
 
 public struct SvgStringUnit : ISvgUnit
 public struct SvgStringUnit : ISvgUnit
 {
 {
@@ -13,7 +15,7 @@ public struct SvgStringUnit : ISvgUnit
         return Value;
         return Value;
     }
     }
 
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
     {
         Value = readerValue;
         Value = readerValue;
     }
     }

+ 6 - 4
src/PixiEditor.SVG/Units/SvgStyleUnit.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.SVG.Units;
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG.Units;
 
 
 public struct SvgStyleUnit : ISvgUnit
 public struct SvgStyleUnit : ISvgUnit
 {
 {
@@ -40,18 +42,18 @@ public struct SvgStyleUnit : ISvgUnit
         return Value;
         return Value;
     }
     }
 
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
     {
         Value = readerValue;
         Value = readerValue;
     }
     }
 
 
-    public TProp TryGetStyleFor<TProp, TUnit>(string property) where TProp : SvgProperty<TUnit> where TUnit : struct, ISvgUnit
+    public TProp TryGetStyleFor<TProp, TUnit>(string property, SvgDefs defs) where TProp : SvgProperty<TUnit> where TUnit : struct, ISvgUnit
     {
     {
         if (inlineDefinedProperties.TryGetValue(property, out var definedProperty))
         if (inlineDefinedProperties.TryGetValue(property, out var definedProperty))
         {
         {
             TProp prop = (TProp)Activator.CreateInstance(typeof(TProp), property);
             TProp prop = (TProp)Activator.CreateInstance(typeof(TProp), property);
             var unit = (TUnit)prop.CreateDefaultUnit();
             var unit = (TUnit)prop.CreateDefaultUnit();
-            unit.ValuesFromXml(definedProperty);
+            unit.ValuesFromXml(definedProperty, defs);
             prop.Unit = unit;
             prop.Unit = unit;
 
 
             return prop;
             return prop;

+ 3 - 2
src/PixiEditor.SVG/Units/SvgTransformUnit.cs

@@ -1,6 +1,7 @@
 using System.Globalization;
 using System.Globalization;
 using System.Numerics;
 using System.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using PixiEditor.SVG.Elements;
 
 
 namespace PixiEditor.SVG.Units;
 namespace PixiEditor.SVG.Units;
 
 
@@ -29,11 +30,11 @@ public struct SvgTransformUnit : ISvgUnit
         return $"matrix({scaleX}, {skewY}, {skewX}, {scaleY}, {translateX}, {translateY})";
         return $"matrix({scaleX}, {skewY}, {skewX}, {scaleY}, {translateX}, {translateY})";
     }
     }
 
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
     {
         if (readerValue.StartsWith("matrix(") && readerValue.EndsWith(")"))
         if (readerValue.StartsWith("matrix(") && readerValue.EndsWith(")"))
         {
         {
-            string[] values = readerValue[7..^1].Split(", ");
+            string[] values = readerValue[7..^1].Replace(" ", "").Split(",");
             if (values.Length == 6)
             if (values.Length == 6)
             {
             {
                 if (float.TryParse(values[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float scaleX) &&
                 if (float.TryParse(values[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float scaleX) &&

+ 4 - 2
src/PixiEditor.SVG/Units/SvgUnit.cs

@@ -1,7 +1,9 @@
-namespace PixiEditor.SVG.Units;
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG.Units;
 
 
 public interface ISvgUnit
 public interface ISvgUnit
 {
 {
     public string ToXml();
     public string ToXml();
-    public void ValuesFromXml(string readerValue);
+    public void ValuesFromXml(string readerValue, SvgDefs defs);
 }
 }

+ 9 - 0
src/PixiEditor/Helpers/Extensions/ColorHelpers.cs

@@ -53,6 +53,10 @@ internal static class ColorHelpers
             radialGradientBrush.RadiusX.Scalar,
             radialGradientBrush.RadiusX.Scalar,
             radialGradientBrush.GradientStops.Select(stop =>
             radialGradientBrush.GradientStops.Select(stop =>
                 new GradientStop(new BackendColor(stop.Color.R, stop.Color.G, stop.Color.B), stop.Offset))),
                 new GradientStop(new BackendColor(stop.Color.R, stop.Color.G, stop.Color.B), stop.Offset))),
+        IConicGradientBrush conicGradientBrush => new SweepGradientPaintable(
+            new VecD(conicGradientBrush.Center.Point.X, conicGradientBrush.Center.Point.Y),
+            conicGradientBrush.GradientStops.Select(stop =>
+                new GradientStop(new BackendColor(stop.Color.R, stop.Color.G, stop.Color.B), stop.Offset))),
 
 
     };
     };
 
 
@@ -72,6 +76,11 @@ internal static class ColorHelpers
             RadiusY = new RelativeScalar(radialGradientPaintable.Radius, RelativeUnit.Absolute),
             RadiusY = new RelativeScalar(radialGradientPaintable.Radius, RelativeUnit.Absolute),
             GradientStops = ToAvaloniaGradientStops(radialGradientPaintable.GradientStops)
             GradientStops = ToAvaloniaGradientStops(radialGradientPaintable.GradientStops)
         },
         },
+        SweepGradientPaintable conicGradientPaintable => new ConicGradientBrush
+        {
+            Center = new RelativePoint(conicGradientPaintable.Center.X, conicGradientPaintable.Center.Y, RelativeUnit.Absolute),
+            GradientStops = ToAvaloniaGradientStops(conicGradientPaintable.GradientStops)
+        },
         _ => throw new NotImplementedException()
         _ => throw new NotImplementedException()
     };
     };
 
 

+ 6 - 4
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -272,8 +272,8 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             return;
             return;
         }
         }
 
 
-        bool hasFill = styleContext.Fill.Unit is { Color.A: > 0 };
-        bool hasStroke = styleContext.Stroke.Unit is { Color.A: > 0 } || styleContext.StrokeWidth.Unit is { PixelsValue: > 0 };
+        bool hasFill = styleContext.Fill.Unit?.Paintable is { AnythingVisible: true };
+        bool hasStroke = styleContext.Stroke.Unit?.Paintable is { AnythingVisible: true } || styleContext.StrokeWidth.Unit is { PixelsValue: > 0 };
         bool hasTransform = styleContext.Transform.Unit is { MatrixValue.IsIdentity: false };
         bool hasTransform = styleContext.Transform.Unit is { MatrixValue.IsIdentity: false };
 
 
         shapeData.Fill = hasFill;
         shapeData.Fill = hasFill;
@@ -281,7 +281,9 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         {
         {
             var target = styleContext.Fill.Unit;
             var target = styleContext.Fill.Unit;
             float opacity = (float)(styleContext.FillOpacity.Unit?.Value ?? 1);
             float opacity = (float)(styleContext.FillOpacity.Unit?.Value ?? 1);
-            shapeData.FillPaintable = target.Value.Color.WithAlpha((byte)(Math.Clamp(opacity, 0, 1) * 255));
+            opacity = Math.Clamp(opacity, 0, 1);
+            shapeData.FillPaintable = target.Value.Paintable;
+            shapeData.FillPaintable?.ApplyOpacity(opacity);
         }
         }
 
 
         if (hasStroke)
         if (hasStroke)
@@ -289,7 +291,7 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             var targetColor = styleContext.Stroke.Unit;
             var targetColor = styleContext.Stroke.Unit;
             var targetWidth = styleContext.StrokeWidth.Unit;
             var targetWidth = styleContext.StrokeWidth.Unit;
 
 
-            shapeData.Stroke = targetColor?.Color ?? Colors.Black;
+            shapeData.Stroke = targetColor?.Paintable ?? Colors.Black;
             shapeData.StrokeWidth = (float)(targetWidth?.PixelsValue ?? 1);
             shapeData.StrokeWidth = (float)(targetWidth?.PixelsValue ?? 1);
         }
         }
 
 

+ 3 - 17
src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -176,18 +176,8 @@ internal partial class DocumentViewModel
             transform = transform.PostConcat(Matrix3X3.CreateScale((float)resizeFactor.X, (float)resizeFactor.Y));
             transform = transform.PostConcat(Matrix3X3.CreateScale((float)resizeFactor.X, (float)resizeFactor.Y));
             primitive.Transform.Unit = new SvgTransformUnit?(new SvgTransformUnit(transform));
             primitive.Transform.Unit = new SvgTransformUnit?(new SvgTransformUnit(transform));
 
 
-            // TODO: add support for other paintables
-            if (data.FillPaintable is ColorPaintable colorPaintable)
-            {
-                primitive.Fill.Unit = SvgColorUnit.FromRgba(colorPaintable.Color.R, colorPaintable.Color.G,
-                    colorPaintable.Color.B, data.Fill ? colorPaintable.Color.A : 0);
-            }
-
-            if (data.Stroke is ColorPaintable strokeColorPaintable)
-            {
-                primitive.Stroke.Unit = SvgColorUnit.FromRgba(strokeColorPaintable.Color.R, strokeColorPaintable.Color.G,
-                    strokeColorPaintable.Color.B, strokeColorPaintable.Color.A);
-            }
+            primitive.Fill.Unit = new SvgPaintServerUnit(data.FillPaintable);
+            primitive.Stroke.Unit = new SvgPaintServerUnit(data.Stroke);
 
 
             primitive.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(data.StrokeWidth);
             primitive.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(data.StrokeWidth);
         }
         }
@@ -213,11 +203,7 @@ internal partial class DocumentViewModel
         line.X2.Unit = SvgNumericUnit.FromUserUnits(lineData.End.X);
         line.X2.Unit = SvgNumericUnit.FromUserUnits(lineData.End.X);
         line.Y2.Unit = SvgNumericUnit.FromUserUnits(lineData.End.Y);
         line.Y2.Unit = SvgNumericUnit.FromUserUnits(lineData.End.Y);
 
 
-        if (lineData.Stroke is ColorPaintable colorPaintable)
-        {
-            line.Stroke.Unit = SvgColorUnit.FromRgba(colorPaintable.Color.R, colorPaintable.Color.G,
-                colorPaintable.Color.B, colorPaintable.Color.A);
-        }
+        line.Stroke.Unit = new SvgPaintServerUnit(lineData.Stroke);
 
 
         line.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(lineData.StrokeWidth);
         line.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(lineData.StrokeWidth);
 
 

+ 1 - 1
src/colorpicker

@@ -1 +1 @@
-Subproject commit 93d8bce4b1ff555e8c6bce99b3a3004cf1a5de69
+Subproject commit 7a7c56b334bbc3a4b017563afe1ea8149f8682d6