Răsfoiți Sursa

Merge branch 'master' into pixiauth

Krzysztof Krysiński 3 luni în urmă
părinte
comite
eab4dfb542

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 13d7c363f5bee90cbde15f1c98cf1744b2bcd493
+Subproject commit 3b9c90113ed6b7600d1b80828d3ddf1d70ead90a

+ 22 - 0
src/PixiEditor.SVG/Elements/SvgPolygon.cs

@@ -0,0 +1,22 @@
+using System.Xml;
+using Drawie.Numerics;
+using PixiEditor.SVG.Units;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgPolygon() : SvgPrimitive("polygon")
+{
+    public SvgProperty<SvgStringUnit> RawPoints { get; } = new SvgProperty<SvgStringUnit>("points");
+    public SvgProperty<SvgNumericUnit> PathLength { get; set; } = new SvgProperty<SvgNumericUnit>("pathLength");
+
+    protected override IEnumerable<SvgProperty> GetProperties()
+    {
+        yield return RawPoints;
+        yield return PathLength;
+    }
+
+    public VecD[] GetPoints()
+    {
+        return SvgPolyline.GetPoints(RawPoints.Unit?.Value ?? string.Empty);
+    }
+}

+ 83 - 0
src/PixiEditor.SVG/Elements/SvgPolyline.cs

@@ -0,0 +1,83 @@
+using System.Xml;
+using Drawie.Numerics;
+using PixiEditor.SVG.Units;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgPolyline() : SvgPrimitive("polyline")
+{
+    public SvgProperty<SvgStringUnit> RawPoints { get; } = new SvgProperty<SvgStringUnit>("points");
+    public SvgProperty<SvgNumericUnit> PathLength { get; set; } = new SvgProperty<SvgNumericUnit>("pathLength");
+
+    protected override IEnumerable<SvgProperty> GetProperties()
+    {
+        yield return RawPoints;
+        yield return PathLength;
+    }
+
+    public VecD[] GetPoints()
+    {
+        return GetPoints(RawPoints.Unit?.Value ?? string.Empty);
+    }
+
+    public static VecD[] GetPoints(string input)
+    {
+        if (string.IsNullOrWhiteSpace(input))
+        {
+            return [];
+        }
+
+        double? x = null;
+        bool nextSpaceIsSeparator = false;
+        string currentNumberString = string.Empty;
+
+        List<VecD> points = new List<VecD>();
+        foreach (char character in input)
+        {
+            if (char.IsWhiteSpace(character))
+            {
+                if (nextSpaceIsSeparator)
+                {
+                    if (x.HasValue)
+                    {
+                        points.Add(new VecD(x.Value, ParseNumber(currentNumberString)));
+                        x = null;
+                        currentNumberString = string.Empty;
+                    }
+
+                    nextSpaceIsSeparator = false;
+                }
+            }
+            else if (char.IsDigit(character) || character == '.' || character == '-' || character == '+')
+            {
+                currentNumberString += character;
+                nextSpaceIsSeparator = x.HasValue;
+            }
+            else if (character == ',')
+            {
+                x = ParseNumber(currentNumberString);
+                currentNumberString = string.Empty;
+                nextSpaceIsSeparator = false;
+            }
+        }
+
+        if (currentNumberString.Length > 0)
+        {
+            if (x.HasValue)
+            {
+                points.Add(new VecD(x.Value, ParseNumber(currentNumberString)));
+            }
+            else
+            {
+                points.Add(new VecD(ParseNumber(currentNumberString), 0));
+            }
+        }
+
+        return points.ToArray();
+    }
+
+    private static double ParseNumber(string currentNumberString)
+    {
+        return double.Parse(currentNumberString, System.Globalization.CultureInfo.InvariantCulture);
+    }
+}

+ 2 - 15
src/PixiEditor.SVG/SvgElement.cs

@@ -113,20 +113,7 @@ public class SvgElement(string tagName)
 
     private void ParseAttribute(SvgProperty property, XmlReader reader, SvgDefs defs)
     {
-        if (property is SvgList list)
-        {
-            ParseListProperty(list, reader, defs);
-        }
-        else
-        {
-            property.Unit ??= property.CreateDefaultUnit();
-            property.Unit.ValuesFromXml(reader.Value, defs);
-        }
-    }
-
-    private void ParseListProperty(SvgList list, XmlReader reader, SvgDefs defs)
-    {
-        list.Unit ??= list.CreateDefaultUnit();
-        list.Unit.ValuesFromXml(reader.Value, defs);
+        property.Unit ??= property.CreateDefaultUnit();
+        property.Unit.ValuesFromXml(reader.Value, defs);
     }
 }

+ 16 - 19
src/PixiEditor.SVG/SvgParser.cs

@@ -1,4 +1,5 @@
 using System.Globalization;
+using System.Reflection;
 using System.Xml;
 using System.Xml.Linq;
 using Drawie.Numerics;
@@ -10,24 +11,10 @@ namespace PixiEditor.SVG;
 
 public class SvgParser
 {
-    private static Dictionary<string, Type> wellKnownElements = new()
-    {
-        { "ellipse", typeof(SvgEllipse) },
-        { "rect", typeof(SvgRectangle) },
-        { "circle", typeof(SvgCircle) },
-        { "line", typeof(SvgLine) },
-        { "path", typeof(SvgPath) },
-        { "g", typeof(SvgGroup) },
-        { "mask", typeof(SvgMask) },
-        { "image", typeof(SvgImage) },
-        { "svg", typeof(SvgDocument) },
-        { "text", typeof(SvgText) },
-        { "linearGradient", typeof(SvgLinearGradient) },
-        { "radialGradient", typeof(SvgRadialGradient) },
-        { "stop", typeof(SvgStop) },
-        { "defs", typeof(SvgDefs) },
-        { "clipPath", typeof(SvgClipPath) }
-    };
+    private static Dictionary<string, Type> wellKnownElements = Assembly.GetExecutingAssembly()
+        .GetTypes()
+        .Where(t => t.IsSubclassOf(typeof(SvgElement)) && !t.IsAbstract)
+        .ToDictionary(t => (Activator.CreateInstance(t) as SvgElement).TagName, t => t);
 
     public string Source { get; set; }
 
@@ -60,12 +47,22 @@ public class SvgParser
         {
             if (reader.NodeType == XmlNodeType.Element)
             {
+                if (reader.LocalName == "defs")
+                {
+                    // already parsed defs, skip
+                    reader.Skip();
+                    if(reader.NodeType != XmlNodeType.Element)
+                    {
+                        continue;
+                    }
+                }
+
                 SvgElement? element = ParseElement(reader, root.Defs);
                 if (element != null)
                 {
                     root.Children.Add(element);
 
-                    if (element is IElementContainer container && element.TagName != "defs")
+                    if (element is IElementContainer container)
                     {
                         ParseChildren(reader, container, root.Defs, element.TagName);
                     }

+ 2 - 0
src/PixiEditor.SVG/SvgProperty.cs

@@ -20,6 +20,8 @@ public abstract class SvgProperty
     public ISvgUnit? Unit { get; set; }
     public string? SvgFullName => NamespaceName == null ? SvgName : $"{NamespaceName}:{SvgName}";
 
+    protected Type? TypeToCreate { get; set; }
+
     public ISvgUnit? CreateDefaultUnit()
     {
         var genericType = this.GetType().GetGenericArguments();

+ 0 - 19
src/PixiEditor.SVG/Units/SvgList.cs

@@ -1,19 +0,0 @@
-namespace PixiEditor.SVG.Units;
-
-public class SvgList : SvgProperty
-{
-    public char Separator { get; set; }
-    public SvgList(string svgName, char separator) : base(svgName)
-    {
-        Separator = separator;
-    }
-}
-
-public class SvgList<T> : SvgList where T : ISvgUnit
-{
-
-    public SvgList(string svgName, char separator, params T[] units) : base(svgName, separator)
-    {
-        
-    }
-}

+ 50 - 0
src/PixiEditor.SVG/Units/SvgVectorUnit.cs

@@ -0,0 +1,50 @@
+using Drawie.Numerics;
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG.Units;
+
+public class SvgVectorUnit : ISvgUnit
+{
+    public SvgNumericUnit X { get; set; }
+    public SvgNumericUnit Y { get; set; }
+
+    public SvgVectorUnit()
+    {
+        X = new SvgNumericUnit();
+        Y = new SvgNumericUnit();
+    }
+
+    public SvgVectorUnit(VecD vector)
+    {
+        X = new SvgNumericUnit(vector.X, "");
+        Y = new SvgNumericUnit(vector.Y, "");
+    }
+
+    public string ToXml(DefStorage defs)
+    {
+        string xValue = X.ToXml(defs);
+        string yValue = Y.ToXml(defs);
+
+        return $"{xValue},{yValue}";
+    }
+
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
+    {
+        if (string.IsNullOrEmpty(readerValue))
+        {
+            X = new SvgNumericUnit();
+            Y = new SvgNumericUnit();
+            return;
+        }
+
+        string[] values =
+            readerValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+        if (values.Length != 2)
+        {
+            throw new FormatException("Invalid vector format. Expected two values.");
+        }
+
+        X.ValuesFromXml(values[0], defs);
+        Y.ValuesFromXml(values[1], defs);
+    }
+}

+ 19 - 0
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -102,6 +102,11 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             shapeData = AddText(text);
             name = TextToolViewModel.NewLayerKey;
         }
+        else if (element is SvgPolyline or SvgPolygon)
+        {
+            shapeData = AddPoly(element);
+            name = VectorPathToolViewModel.NewLayerKey;
+        }
 
         name = element.Id.Unit?.Value ?? name;
 
@@ -376,6 +381,20 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         };
     }
 
+    private PathVectorData AddPoly(SvgElement element)
+    {
+        if (element is SvgPolyline polyline)
+        {
+            return new PathVectorData(VectorPath.FromPoints(polyline.GetPoints(), false));
+        }
+        if (element is SvgPolygon polygon)
+        {
+            return new PathVectorData(VectorPath.FromPoints(polygon.GetPoints(), true));
+        }
+
+        return null;
+    }
+
     private void AddCommonShapeData(ShapeVectorData? shapeData, StyleContext styleContext)
     {
         if (shapeData == null)

+ 46 - 6
src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs

@@ -62,6 +62,9 @@ public class VectorPathOverlay : Overlay
     private bool canInsert = false;
 
     private bool isDragging = false;
+    private bool pointerPressed = false;
+    private bool convertSelectedOnDrag = false;
+    private bool converted = false;
 
     private List<int> lastSelectedIndices = new();
 
@@ -515,6 +518,8 @@ public class VectorPathOverlay : Overlay
             return;
         }
 
+        pointerPressed = true;
+
         if (IsOverPath(args.Point, out VecD closestPoint))
         {
             AddPointAt(closestPoint);
@@ -524,12 +529,43 @@ public class VectorPathOverlay : Overlay
         else if (args.Modifiers == KeyModifiers.None)
         {
             args.Handled = AddNewPointFromClick(SnappingController.GetSnapPoint(args.Point, out _, out _));
+            if (args.Handled)
+            {
+                convertSelectedOnDrag = true;
+                converted = false;
+            }
+
             AddToUndoCommand.Execute(Path);
         }
     }
 
     protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
     {
+        if (pointerPressed && convertSelectedOnDrag)
+        {
+            var anchor = anchorHandles.FirstOrDefault(h => h.IsSelected);
+            if (anchor == null)
+            {
+                return;
+            }
+
+            int index = anchorHandles.IndexOf(anchor);
+            var path = editableVectorPath;
+            if (!converted)
+            {
+                path = ConvertTouchingVerbsToCubic(anchor);
+                Path = path.ToVectorPath();
+                AdjustHandles(path);
+                converted = true;
+            }
+
+            SubShape subShapeContainingIndex = path.GetSubShapeContainingIndex(index);
+            int localIndex = path.GetSubShapePointIndex(index, subShapeContainingIndex);
+
+            HandleContinousCubicDrag(args.Point, subShapeContainingIndex, localIndex, true);
+            Path = editableVectorPath.ToVectorPath();
+        }
+
         if (IsOverPath(args.Point, out VecD closestPoint))
         {
             insertPreviewHandle.Position = closestPoint;
@@ -541,10 +577,12 @@ public class VectorPathOverlay : Overlay
         }
     }
 
-    protected override void OnPointerReleased(PointerReleasedEventArgs e)
+    protected override void OnOverlayPointerReleased(OverlayPointerArgs args)
     {
-        base.OnPointerReleased(e);
         isDragging = false;
+        pointerPressed = false;
+        convertSelectedOnDrag = false;
+        converted = false;
     }
 
     private bool AddNewPointFromClick(VecD point)
@@ -586,7 +624,9 @@ public class VectorPathOverlay : Overlay
     {
         int? insertedAt = editableVectorPath.AddPointAt(point);
         Path = editableVectorPath.ToVectorPath();
-        SelectAnchor(insertedAt is > 0 && insertedAt.Value < anchorHandles.Count ? anchorHandles[insertedAt.Value] : anchorHandles.Last());
+        SelectAnchor(insertedAt is > 0 && insertedAt.Value < anchorHandles.Count
+            ? anchorHandles[insertedAt.Value]
+            : anchorHandles.Last());
     }
 
     private bool IsOverPath(VecD point, out VecD closestPoint)
@@ -669,7 +709,7 @@ public class VectorPathOverlay : Overlay
                 subShapeContainingIndex = newPath.GetSubShapeContainingIndex(index);
                 localIndex = newPath.GetSubShapePointIndex(index, subShapeContainingIndex);
 
-                HandleContinousCubicDrag(targetPos, anchor, subShapeContainingIndex, localIndex, true);
+                HandleContinousCubicDrag(targetPos, subShapeContainingIndex, localIndex, true);
             }
             else
             {
@@ -687,7 +727,7 @@ public class VectorPathOverlay : Overlay
         Path = editableVectorPath.ToVectorPath();
     }
 
-    private void HandleContinousCubicDrag(VecD targetPos, AnchorHandle handle, SubShape subShapeContainingIndex,
+    private void HandleContinousCubicDrag(VecD targetPos, SubShape subShapeContainingIndex,
         int localIndex, bool constrainRatio, bool swapOrder = false)
     {
         var previousPoint = subShapeContainingIndex.GetPreviousPoint(localIndex);
@@ -756,7 +796,7 @@ public class VectorPathOverlay : Overlay
         {
             bool isDraggingFirst = controlPointHandles.IndexOf(controlPointHandle) % 2 == 0;
             bool constrainRatio = args.Modifiers.HasFlag(KeyModifiers.Control);
-            HandleContinousCubicDrag(targetPos, to, subShapeContainingIndex, localIndex, constrainRatio,
+            HandleContinousCubicDrag(targetPos, subShapeContainingIndex, localIndex, constrainRatio,
                 !isDraggingFirst);
         }
         else