Jelajahi Sumber

Added parsing inline styles

Krzysztof Krysiński 5 bulan lalu
induk
melakukan
c7a78e03db

+ 1 - 0
src/PixiEditor.SVG/Elements/SvgGroup.cs

@@ -10,6 +10,7 @@ public class SvgGroup() : SvgElement("g"), ITransformable, IFillable, IStrokable
     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<SvgColorUnit> Fill { get; } = new("fill");
+    public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
     public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgColorUnit> 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");

+ 2 - 0
src/PixiEditor.SVG/Elements/SvgPrimitive.cs

@@ -9,6 +9,7 @@ public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITrans
 {
 {
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
     public SvgProperty<SvgColorUnit> Fill { get; } = new("fill");
     public SvgProperty<SvgColorUnit> Fill { get; } = new("fill");
+    public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
     public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
     
     
@@ -24,6 +25,7 @@ public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITrans
         
         
         properties.Add(Transform);
         properties.Add(Transform);
         properties.Add(Fill);
         properties.Add(Fill);
+        properties.Add(FillOpacity);
         properties.Add(Stroke);
         properties.Add(Stroke);
         properties.Add(StrokeWidth);
         properties.Add(StrokeWidth);
         properties.Add(StrokeLineCap);
         properties.Add(StrokeLineCap);

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

@@ -35,7 +35,7 @@ public class SvgText() : SvgPrimitive("text")
         string content = string.Empty;
         string content = string.Empty;
         while (reader.Read())
         while (reader.Read())
         {
         {
-            if (reader.NodeType == XmlNodeType.Text)
+            if (reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.CDATA)
             {
             {
                 content = reader.Value;
                 content = reader.Value;
             }
             }

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

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

+ 115 - 32
src/PixiEditor.SVG/StyleContext.cs

@@ -11,10 +11,12 @@ public struct StyleContext
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; }
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; }
     public SvgProperty<SvgColorUnit> Stroke { get; }
     public SvgProperty<SvgColorUnit> Stroke { get; }
     public SvgProperty<SvgColorUnit> Fill { get; }
     public SvgProperty<SvgColorUnit> Fill { 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; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineJoin>> StrokeLineJoin { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineJoin>> StrokeLineJoin { get; }
     public SvgProperty<SvgNumericUnit> Opacity { get; }
     public SvgProperty<SvgNumericUnit> Opacity { get; }
+    public SvgProperty<SvgStyleUnit> InlineStyle { get; set; }
     public VecD ViewboxOrigin { get; set; }
     public VecD ViewboxOrigin { get; set; }
 
 
     public StyleContext()
     public StyleContext()
@@ -22,78 +24,89 @@ public struct StyleContext
         StrokeWidth = new("stroke-width");
         StrokeWidth = new("stroke-width");
         Stroke = new("stroke");
         Stroke = new("stroke");
         Fill = new("fill");
         Fill = new("fill");
+        FillOpacity = new("fill-opacity");
         Fill.Unit = new SvgColorUnit?(new SvgColorUnit("black"));
         Fill.Unit = new SvgColorUnit?(new SvgColorUnit("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");
     }
     }
-    
+
     public StyleContext(SvgDocument document)
     public StyleContext(SvgDocument document)
     {
     {
-        StrokeWidth = document.StrokeWidth;
-        Stroke = document.Stroke;
-        Fill = document.Fill;
-        Transform = document.Transform;
-        StrokeLineCap = document.StrokeLineCap;
-        StrokeLineJoin = document.StrokeLineJoin;
-        Opacity = document.Opacity;
+        StrokeWidth = FallbackToCssStyle(document.StrokeWidth, document.Style);
+        Stroke = FallbackToCssStyle(document.Stroke, document.Style);
+        Fill = FallbackToCssStyle(document.Fill, document.Style, new SvgColorUnit("black"));
+        FillOpacity = FallbackToCssStyle(document.FillOpacity, document.Style);
+        Transform = FallbackToCssStyle(document.Transform, document.Style, new SvgTransformUnit(Matrix3X3.Identity));
+        StrokeLineCap = FallbackToCssStyle(document.StrokeLineCap, document.Style);
+        StrokeLineJoin = FallbackToCssStyle(document.StrokeLineJoin, document.Style);
+        Opacity = FallbackToCssStyle(document.Opacity, document.Style);
         ViewboxOrigin = new VecD(
         ViewboxOrigin = new VecD(
             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;
     }
     }
 
 
     public StyleContext WithElement(SvgElement element)
     public StyleContext WithElement(SvgElement element)
     {
     {
         StyleContext styleContext = Copy();
         StyleContext styleContext = Copy();
 
 
+        styleContext.InlineStyle = MergeInlineStyle(element.Style, InlineStyle);
+
         if (element is ITransformable transformableElement)
         if (element is ITransformable transformableElement)
         {
         {
             if (styleContext.Transform.Unit == null)
             if (styleContext.Transform.Unit == null)
             {
             {
-                styleContext.Transform.Unit = transformableElement.Transform.Unit;
+                styleContext.Transform.Unit =
+                    FallbackToCssStyle(transformableElement.Transform, styleContext.Transform, styleContext.InlineStyle)
+                        .Unit;
             }
             }
             else
             else
             {
             {
                 styleContext.Transform.Unit = new SvgTransformUnit(
                 styleContext.Transform.Unit = new SvgTransformUnit(
                     styleContext.Transform.Unit.Value.MatrixValue.Concat(
                     styleContext.Transform.Unit.Value.MatrixValue.Concat(
-                        transformableElement.Transform.Unit?.MatrixValue ?? Matrix3X3.Identity));
+                        FallbackToCssStyle(transformableElement.Transform, styleContext.InlineStyle).Unit
+                            ?.MatrixValue ??
+                        Matrix3X3.Identity));
             }
             }
         }
         }
 
 
-        if (element is IFillable { Fill.Unit: not null } fillableElement)
+        if (element is IFillable fillableElement)
         {
         {
-            styleContext.Fill.Unit = fillableElement.Fill.Unit;
+            styleContext.Fill.Unit = FallbackToCssStyle(fillableElement.Fill, styleContext.Fill,
+                styleContext.InlineStyle, new SvgColorUnit("black")).Unit;
+            styleContext.FillOpacity.Unit =
+                FallbackToCssStyle(fillableElement.FillOpacity, styleContext.FillOpacity, styleContext.InlineStyle)
+                    .Unit;
         }
         }
 
 
         if (element is IStrokable strokableElement)
         if (element is IStrokable strokableElement)
         {
         {
-            if (strokableElement.Stroke.Unit != null)
-            {
-                styleContext.Stroke.Unit = strokableElement.Stroke.Unit;
-            }
+            styleContext.Stroke.Unit =
+                FallbackToCssStyle(strokableElement.Stroke, styleContext.Stroke, styleContext.InlineStyle).Unit;
 
 
-            if (strokableElement.StrokeWidth.Unit != null)
-            {
-                styleContext.StrokeWidth.Unit = strokableElement.StrokeWidth.Unit;
-            }
-            
-            if (strokableElement.StrokeLineCap.Unit != null)
-            {
-                styleContext.StrokeLineCap.Unit = strokableElement.StrokeLineCap.Unit;
-            }
-            
-            if (strokableElement.StrokeLineJoin.Unit != null)
-            {
-                styleContext.StrokeLineJoin.Unit = strokableElement.StrokeLineJoin.Unit;
-            }
+            styleContext.StrokeWidth.Unit =
+                FallbackToCssStyle(strokableElement.StrokeWidth, styleContext.StrokeWidth, styleContext.InlineStyle)
+                    .Unit;
+
+            styleContext.StrokeLineCap.Unit =
+                FallbackToCssStyle(strokableElement.StrokeLineCap, styleContext.StrokeLineCap, styleContext.InlineStyle)
+                    .Unit;
+
+            styleContext.StrokeLineJoin.Unit =
+                FallbackToCssStyle(strokableElement.StrokeLineJoin, styleContext.StrokeLineJoin,
+                    styleContext.InlineStyle).Unit;
         }
         }
 
 
-        if(element is IOpacity opacityElement)
+        if (element is IOpacity opacityElement)
         {
         {
-            styleContext.Opacity.Unit = opacityElement.Opacity.Unit;
+            styleContext.Opacity.Unit =
+                FallbackToCssStyle(opacityElement.Opacity, styleContext.Opacity, styleContext.InlineStyle).Unit;
         }
         }
 
 
+
         return styleContext;
         return styleContext;
     }
     }
 
 
@@ -115,6 +128,11 @@ public struct StyleContext
             styleContext.Fill.Unit = Fill.Unit;
             styleContext.Fill.Unit = Fill.Unit;
         }
         }
 
 
+        if (FillOpacity.Unit != null)
+        {
+            styleContext.FillOpacity.Unit = FillOpacity.Unit;
+        }
+
         if (Transform.Unit != null)
         if (Transform.Unit != null)
         {
         {
             styleContext.Transform.Unit = Transform.Unit;
             styleContext.Transform.Unit = Transform.Unit;
@@ -137,6 +155,71 @@ public struct StyleContext
 
 
         styleContext.ViewboxOrigin = ViewboxOrigin;
         styleContext.ViewboxOrigin = ViewboxOrigin;
 
 
+        if (InlineStyle.Unit != null)
+        {
+            styleContext.InlineStyle.Unit = InlineStyle.Unit;
+        }
+
         return styleContext;
         return styleContext;
     }
     }
+
+
+    private SvgProperty<TUnit>? FallbackToCssStyle<TUnit>(
+        SvgProperty<TUnit> property,
+        SvgProperty<SvgStyleUnit> inlineStyle, TUnit? fallback = null) where TUnit : struct, ISvgUnit
+    {
+        if (property.Unit != null)
+        {
+            return property;
+        }
+
+        SvgStyleUnit? style = inlineStyle.Unit;
+        return style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName)
+               ?? (fallback.HasValue
+                   ? new SvgProperty<TUnit>(property.SvgName) { Unit = fallback.Value }
+                   : new SvgProperty<TUnit>(property.SvgName));
+    }
+
+    private SvgProperty<TUnit>? FallbackToCssStyle<TUnit>(
+        SvgProperty<TUnit> property,
+        SvgProperty<TUnit> parentStyleProperty,
+        SvgProperty<SvgStyleUnit> inlineStyle, TUnit? fallback = null) where TUnit : struct, ISvgUnit
+    {
+        if (property.Unit != null)
+        {
+            return property;
+        }
+
+        SvgStyleUnit? style = inlineStyle.Unit;
+        var styleProp = style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName);
+        if (styleProp != null) return styleProp;
+        if(parentStyleProperty.Unit != null)
+        {
+            return parentStyleProperty;
+        }
+
+        return (fallback.HasValue
+            ? new SvgProperty<TUnit>(property.SvgName) { Unit = fallback.Value }
+            : new SvgProperty<TUnit>(property.SvgName));
+    }
+
+    private SvgProperty<SvgStyleUnit> MergeInlineStyle(SvgProperty<SvgStyleUnit> elementStyle,
+        SvgProperty<SvgStyleUnit> parentStyle)
+    {
+        SvgStyleUnit? elementStyleUnit = elementStyle.Unit;
+        SvgStyleUnit? parentStyleUnit = parentStyle.Unit;
+
+        if (elementStyleUnit == null)
+        {
+            return parentStyle;
+        }
+
+        if (parentStyleUnit == null)
+        {
+            return elementStyle;
+        }
+
+        SvgStyleUnit style = parentStyleUnit.Value.MergeWith(elementStyleUnit.Value);
+        return new SvgProperty<SvgStyleUnit>("style") { Unit = style };
+    }
 }
 }

+ 12 - 11
src/PixiEditor.SVG/SvgDocument.cs

@@ -11,24 +11,24 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
 {
 {
     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> Fill { get; } = new("fill");
     public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgColorUnit> 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<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 List<SvgElement> Children { get; } = new();
     public List<SvgElement> Children { get; } = new();
 
 
     public SvgDocument() : base("svg")
     public SvgDocument() : base("svg")
     {
     {
-        
     }
     }
-    
+
     public SvgDocument(RectD viewBox) : base("svg")
     public SvgDocument(RectD viewBox) : base("svg")
     {
     {
         ViewBox.Unit = new SvgRectUnit(viewBox);
         ViewBox.Unit = new SvgRectUnit(viewBox);
@@ -39,6 +39,7 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
         List<SvgProperty> properties = new()
         List<SvgProperty> properties = new()
         {
         {
             Fill,
             Fill,
+            FillOpacity,
             Stroke,
             Stroke,
             StrokeWidth,
             StrokeWidth,
             Transform,
             Transform,
@@ -47,7 +48,7 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
             StrokeLineJoin,
             StrokeLineJoin,
             Opacity
             Opacity
         };
         };
-        
+
         ParseAttributes(properties, reader);
         ParseAttributes(properties, reader);
     }
     }
 
 
@@ -104,11 +105,11 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
 
 
     private void AppendProperties(XElement? root)
     private void AppendProperties(XElement? root)
     {
     {
-        if(ViewBox.Unit != null)
+        if (ViewBox.Unit != null)
         {
         {
             root.Add(new XAttribute("viewBox", ViewBox.Unit.Value.ToXml()));
             root.Add(new XAttribute("viewBox", ViewBox.Unit.Value.ToXml()));
         }
         }
-        
+
         if (Fill.Unit != null)
         if (Fill.Unit != null)
         {
         {
             root.Add(new XAttribute("fill", Fill.Unit.Value.ToXml()));
             root.Add(new XAttribute("fill", Fill.Unit.Value.ToXml()));
@@ -123,17 +124,17 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
         {
         {
             root.Add(new XAttribute("stroke-width", StrokeWidth.Unit.Value.ToXml()));
             root.Add(new XAttribute("stroke-width", StrokeWidth.Unit.Value.ToXml()));
         }
         }
-        
+
         if (Transform.Unit != null)
         if (Transform.Unit != null)
         {
         {
             root.Add(new XAttribute("transform", Transform.Unit.Value.ToXml()));
             root.Add(new XAttribute("transform", Transform.Unit.Value.ToXml()));
         }
         }
-        
+
         if (StrokeLineCap.Unit != null)
         if (StrokeLineCap.Unit != null)
         {
         {
             root.Add(new XAttribute("stroke-linecap", StrokeLineCap.Unit.Value.ToXml()));
             root.Add(new XAttribute("stroke-linecap", StrokeLineCap.Unit.Value.ToXml()));
         }
         }
-        
+
         if (StrokeLineJoin.Unit != null)
         if (StrokeLineJoin.Unit != null)
         {
         {
             root.Add(new XAttribute("stroke-linejoin", StrokeLineJoin.Unit.Value.ToXml()));
             root.Add(new XAttribute("stroke-linejoin", StrokeLineJoin.Unit.Value.ToXml()));

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

@@ -13,6 +13,7 @@ public class SvgElement(string tagName)
     public Dictionary<string, string> RequiredNamespaces { get; } = new();
     public Dictionary<string, string> RequiredNamespaces { get; } = new();
     public string TagName { get; } = tagName;
     public string TagName { get; } = tagName;
 
 
+    public SvgProperty<SvgStyleUnit> Style { get; } = new("style");
 
 
     public XElement ToXml(XNamespace nameSpace)
     public XElement ToXml(XNamespace nameSpace)
     {
     {
@@ -64,6 +65,11 @@ public class SvgElement(string tagName)
 
 
     protected void ParseAttributes(List<SvgProperty> properties, XmlReader reader)
     protected void ParseAttributes(List<SvgProperty> properties, XmlReader reader)
     {
     {
+        if (!properties.Contains(Style))
+        {
+            properties.Insert(0, Style);
+        }
+
         do
         do
         {
         {
             SvgProperty matchingProperty = properties.FirstOrDefault(x =>
             SvgProperty matchingProperty = properties.FirstOrDefault(x =>
@@ -83,31 +89,14 @@ public class SvgElement(string tagName)
         }
         }
         else
         else
         {
         {
-            property.Unit ??= CreateDefaultUnit(property);
+            property.Unit ??= property.CreateDefaultUnit();
             property.Unit.ValuesFromXml(reader.Value);
             property.Unit.ValuesFromXml(reader.Value);
         }
         }
     }
     }
 
 
     private void ParseListProperty(SvgList list, XmlReader reader)
     private void ParseListProperty(SvgList list, XmlReader reader)
     {
     {
-        list.Unit ??= CreateDefaultUnit(list);
+        list.Unit ??= list.CreateDefaultUnit();
         list.Unit.ValuesFromXml(reader.Value);
         list.Unit.ValuesFromXml(reader.Value);
     }
     }
-
-    private ISvgUnit CreateDefaultUnit(SvgProperty property)
-    {
-        var genericType = property.GetType().GetGenericArguments();
-        if (genericType.Length == 0)
-        {
-            throw new InvalidOperationException("Property does not have a generic type");
-        }
-
-        ISvgUnit unit = Activator.CreateInstance(genericType[0]) as ISvgUnit;
-        if (unit == null)
-        {
-            throw new InvalidOperationException("Could not create unit");
-        }
-
-        return unit;
-    }
 }
 }

+ 10 - 9
src/PixiEditor.SVG/SvgParser.cs

@@ -1,4 +1,5 @@
-using System.Xml;
+using System.Globalization;
+using System.Xml;
 using System.Xml.Linq;
 using System.Xml.Linq;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Elements;
@@ -130,16 +131,16 @@ public class SvgParser
             string[] parts = viewBox.Split(' ').Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
             string[] parts = viewBox.Split(' ').Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
             if (parts.Length == 4)
             if (parts.Length == 4)
             {
             {
-                finalX = double.Parse(parts[0]);
-                finalY = double.Parse(parts[1]);
-                finalWidth = double.Parse(parts[2]);
-                finalHeight = double.Parse(parts[3]);
+                finalX = double.Parse(parts[0], CultureInfo.InvariantCulture);
+                finalY = double.Parse(parts[1], CultureInfo.InvariantCulture);
+                finalWidth = double.Parse(parts[2], CultureInfo.InvariantCulture);
+                finalHeight = double.Parse(parts[3], CultureInfo.InvariantCulture);
             }
             }
         }
         }
 
 
         if (x != null)
         if (x != null)
         {
         {
-            if (double.TryParse(x, out double xValue))
+            if (double.TryParse(x, CultureInfo.InvariantCulture, out double xValue))
             {
             {
                 finalX = xValue;
                 finalX = xValue;
             }
             }
@@ -147,7 +148,7 @@ public class SvgParser
 
 
         if (y != null)
         if (y != null)
         {
         {
-            if (double.TryParse(y, out double yValue))
+            if (double.TryParse(y, CultureInfo.InvariantCulture, out double yValue))
             {
             {
                 finalY = yValue;
                 finalY = yValue;
             }
             }
@@ -155,7 +156,7 @@ public class SvgParser
 
 
         if (width != null)
         if (width != null)
         {
         {
-            if (double.TryParse(width, out double widthValue))
+            if (double.TryParse(width, CultureInfo.InvariantCulture, out double widthValue))
             {
             {
                 finalWidth = widthValue;
                 finalWidth = widthValue;
             }
             }
@@ -163,7 +164,7 @@ public class SvgParser
 
 
         if (height != null)
         if (height != null)
         {
         {
-            if (double.TryParse(height, out double heightValue))
+            if (double.TryParse(height, CultureInfo.InvariantCulture, out double heightValue))
             {
             {
                 finalHeight = heightValue;
                 finalHeight = heightValue;
             }
             }

+ 20 - 3
src/PixiEditor.SVG/SvgProperty.cs

@@ -9,7 +9,7 @@ public abstract class SvgProperty
     {
     {
         SvgName = svgName;
         SvgName = svgName;
     }
     }
-    
+
     protected SvgProperty(string svgName, string? namespaceName) : this(svgName)
     protected SvgProperty(string svgName, string? namespaceName) : this(svgName)
     {
     {
         NamespaceName = namespaceName;
         NamespaceName = namespaceName;
@@ -18,6 +18,23 @@ public abstract class SvgProperty
     public string? NamespaceName { get; set; }
     public string? NamespaceName { get; set; }
     public string SvgName { get; set; }
     public string SvgName { get; set; }
     public ISvgUnit? Unit { get; set; }
     public ISvgUnit? Unit { get; set; }
+
+    public ISvgUnit? CreateDefaultUnit()
+    {
+        var genericType = this.GetType().GetGenericArguments();
+        if (genericType.Length == 0)
+        {
+            return null;
+        }
+
+        ISvgUnit unit = Activator.CreateInstance(genericType[0]) as ISvgUnit;
+        if (unit == null)
+        {
+            throw new InvalidOperationException("Could not create unit");
+        }
+
+        return unit;
+    }
 }
 }
 
 
 public class SvgProperty<T> : SvgProperty where T : struct, ISvgUnit
 public class SvgProperty<T> : SvgProperty where T : struct, ISvgUnit
@@ -27,11 +44,11 @@ public class SvgProperty<T> : SvgProperty where T : struct, ISvgUnit
         get => (T?)base.Unit;
         get => (T?)base.Unit;
         set => base.Unit = value;
         set => base.Unit = value;
     }
     }
-    
+
     public SvgProperty(string svgName) : base(svgName)
     public SvgProperty(string svgName) : base(svgName)
     {
     {
     }
     }
-    
+
     public SvgProperty(string svgName, string? namespaceName) : base(svgName, namespaceName)
     public SvgProperty(string svgName, string? namespaceName) : base(svgName, namespaceName)
     {
     {
     }
     }

+ 73 - 0
src/PixiEditor.SVG/Units/SvgStyleUnit.cs

@@ -0,0 +1,73 @@
+namespace PixiEditor.SVG.Units;
+
+public struct SvgStyleUnit : ISvgUnit
+{
+    private Dictionary<string, string> inlineDefinedProperties;
+    private string value;
+
+    public SvgStyleUnit(string inlineStyle)
+    {
+        Value = inlineStyle;
+    }
+
+    public string Value
+    {
+        get => value;
+        set
+        {
+            this.value = value;
+            inlineDefinedProperties = new Dictionary<string, string>();
+
+            if (string.IsNullOrEmpty(value))
+            {
+                return;
+            }
+
+            string[] properties = value.Split(';');
+            foreach (string property in properties)
+            {
+                string[] keyValue = property.Split(':');
+                if (keyValue.Length == 2)
+                {
+                    inlineDefinedProperties.Add(keyValue[0].Trim(), keyValue[1].Trim());
+                }
+            }
+        }
+    }
+
+    public string ToXml()
+    {
+        return Value;
+    }
+
+    public void ValuesFromXml(string readerValue)
+    {
+        Value = readerValue;
+    }
+
+    public TProp TryGetStyleFor<TProp, TUnit>(string property) where TProp : SvgProperty<TUnit> where TUnit : struct, ISvgUnit
+    {
+        if (inlineDefinedProperties.TryGetValue(property, out var definedProperty))
+        {
+            TProp prop = (TProp)Activator.CreateInstance(typeof(TProp), property);
+            var unit = (TUnit)prop.CreateDefaultUnit();
+            unit.ValuesFromXml(definedProperty);
+            prop.Unit = unit;
+
+            return prop;
+        }
+
+        return null;
+    }
+
+    public SvgStyleUnit MergeWith(SvgStyleUnit elementStyleUnit)
+    {
+        Dictionary<string, string> props = new(inlineDefinedProperties);
+        foreach (var inlineDefined in elementStyleUnit.inlineDefinedProperties)
+        {
+            props[inlineDefined.Key] = inlineDefined.Value;
+        }
+
+        return new SvgStyleUnit(string.Join(";", props.Select(x => $"{x.Key}:{x.Value}")));
+    }
+}

+ 2 - 1
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -278,7 +278,8 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         if (hasFill)
         if (hasFill)
         {
         {
             var target = styleContext.Fill.Unit;
             var target = styleContext.Fill.Unit;
-            shapeData.FillColor = target.Value.Color;
+            float opacity = (float)(styleContext.FillOpacity.Unit?.Value ?? 1);
+            shapeData.FillColor = target.Value.Color.WithAlpha((byte)(Math.Clamp(opacity, 0, 1) * 255));
         }
         }
 
 
         if (hasStroke)
         if (hasStroke)