2
0
Krzysztof Krysiński 5 сар өмнө
parent
commit
ff8e2595d2

+ 5 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/Shapes/IReadOnlyTextData.cs

@@ -1,4 +1,5 @@
-using Drawie.Numerics;
+using Drawie.Backend.Core.Text;
+using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
 
@@ -6,4 +7,7 @@ public interface IReadOnlyTextData
 {
     public string Text { get; }
     public VecD Position { get; }
+    public Font ConstructFont();
+    public double Spacing { get; }
+    public double MaxWidth { get; }
 }

+ 16 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs

@@ -29,6 +29,7 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData
 
     public VecD Position { get; set; }
 
+
     public double MaxWidth { get; set; } = double.MaxValue;
 
     public Font Font
@@ -61,6 +62,20 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData
         lastBounds = richText.MeasureBounds(Font);
     }
 
+    public Font ConstructFont()
+    {
+        Font newFont = Font.FromFontFamily(Font.Family);
+        newFont.Size = Font.Size;
+        newFont.Edging = Font.Edging;
+        newFont.SubPixel = Font.SubPixel;
+        newFont.Bold = Font.Bold;
+        newFont.Italic = Font.Italic;
+
+        return newFont;
+    }
+
+    double IReadOnlyTextData.Spacing => Spacing ?? Font.Size;
+
     public double? Spacing
     {
         get => spacing;
@@ -103,6 +118,7 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData
 
     private RichText richText;
     private RectD lastBounds;
+    private double _spacing;
 
     public TextVectorData()
     {

+ 9 - 0
src/PixiEditor.SVG/Elements/SvgText.cs

@@ -1,4 +1,5 @@
 using System.Xml;
+using PixiEditor.SVG.Enums;
 using PixiEditor.SVG.Units;
 
 namespace PixiEditor.SVG.Elements;
@@ -8,6 +9,10 @@ public class SvgText() : SvgPrimitive("text")
     public SvgProperty<SvgStringUnit> Text { get; } = new("");
     public SvgProperty<SvgNumericUnit> X { get; } = new("x");
     public SvgProperty<SvgNumericUnit> Y { get; } = new("y");
+    public SvgProperty<SvgNumericUnit> FontSize { get; } = new("font-size");
+    public SvgProperty<SvgStringUnit> FontFamily { get; } = new("font-family");
+    public SvgProperty<SvgEnumUnit<SvgFontWeight>> FontWeight { get; } = new("font-weight");
+    public SvgProperty<SvgEnumUnit<SvgFontStyle>> FontStyle { get; } = new("font-style");
 
     public override void ParseData(XmlReader reader)
     {
@@ -19,6 +24,10 @@ public class SvgText() : SvgPrimitive("text")
     {
         yield return X;
         yield return Y;
+        yield return FontSize;
+        yield return FontFamily;
+        yield return FontWeight;
+        yield return FontStyle;
     }
 
     private string ParseContent(XmlReader reader)

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

@@ -0,0 +1,8 @@
+namespace PixiEditor.SVG.Enums;
+
+public enum SvgFontStyle
+{
+    Normal,
+    Italic,
+    Oblique,
+}

+ 9 - 0
src/PixiEditor.SVG/Enums/SvgFontWeight.cs

@@ -0,0 +1,9 @@
+namespace PixiEditor.SVG.Enums;
+
+public enum SvgFontWeight
+{
+    Lighter = 100,
+    Normal = 400,
+    Bold = 700,
+    Bolder = 900,
+}

+ 11 - 4
src/PixiEditor.SVG/SvgElement.cs

@@ -25,14 +25,21 @@ public class SvgElement(string tagName)
                 SvgProperty prop = (SvgProperty)property.GetValue(this);
                 if (prop?.Unit != null)
                 {
-                    if (!string.IsNullOrEmpty(prop.NamespaceName))
+                    if (string.IsNullOrEmpty(prop.SvgName))
                     {
-                        XName name = XNamespace.Get(RequiredNamespaces[prop.NamespaceName]) + prop.SvgName;
-                        element.Add(new XAttribute(name, prop.Unit.ToXml()));
+                        element.Value = prop.Unit.ToXml();
                     }
                     else
                     {
-                        element.Add(new XAttribute(prop.SvgName, prop.Unit.ToXml()));
+                        if (!string.IsNullOrEmpty(prop.NamespaceName))
+                        {
+                            XName name = XNamespace.Get(RequiredNamespaces[prop.NamespaceName]) + prop.SvgName;
+                            element.Add(new XAttribute(name, prop.Unit.ToXml()));
+                        }
+                        else
+                        {
+                            element.Add(new XAttribute(prop.SvgName, prop.Unit.ToXml()));
+                        }
                     }
                 }
             }

+ 2 - 2
src/PixiEditor.SVG/SvgParser.cs

@@ -36,7 +36,7 @@ public class SvgParser
         using var reader = document.CreateReader();
 
         XmlNodeType node = reader.MoveToContent();
-        if (node != XmlNodeType.Element || reader.Name != "svg")
+        if (node != XmlNodeType.Element || reader.LocalName != "svg")
         {
             return null;
         }
@@ -93,7 +93,7 @@ public class SvgParser
 
     private SvgElement? ParseElement(XmlReader reader)
     {
-        if (wellKnownElements.TryGetValue(reader.Name, out Type elementType))
+        if (wellKnownElements.TryGetValue(reader.LocalName, out Type elementType))
         {
             SvgElement element = (SvgElement)Activator.CreateInstance(elementType);
             if (reader.MoveToFirstAttribute())

+ 2 - 0
src/PixiEditor/Models/Files/SvgFileType.cs

@@ -26,6 +26,8 @@ internal class SvgFileType : IoFileType
         job?.Report(0.5, string.Empty); 
         string xml = svgDocument.ToXml();
 
+        xml = $"<!-- Created with PixiEditor (https://pixieditor.net) -->{Environment.NewLine}" + xml;
+
         job?.Report(0.75, string.Empty);
         await using FileStream fileStream = new(pathWithExtension, FileMode.Create);
         await using StreamWriter writer = new(fileStream);

+ 35 - 8
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -2,6 +2,7 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
@@ -9,10 +10,12 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Dialogs;
 using PixiEditor.Parser.Graph;
 using PixiEditor.SVG;
 using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Enums;
+using PixiEditor.SVG.Exceptions;
 using PixiEditor.ViewModels.Tools.Tools;
 
 namespace PixiEditor.Models.IO.CustomDocumentFormats;
@@ -26,6 +29,11 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         string xml = File.ReadAllText(path);
         SvgDocument document = SvgDocument.Parse(xml);
 
+        if(document == null)
+        {
+            throw new SvgParsingException("Failed to parse SVG document");
+        }
+
         StyleContext styleContext = new(document);
 
         VecI size = new((int)document.ViewBox.Unit.Value.Value.Width, (int)document.ViewBox.Unit.Value.Value.Height);
@@ -75,7 +83,7 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         }
         else if (element is SvgPath pathElement)
         {
-            shapeData = AddPath(pathElement);
+            shapeData = AddPath(pathElement, styleContext);
             name = VectorPathToolViewModel.NewLayerKey;
         }
         else if (element is SvgRectangle rect)
@@ -189,7 +197,7 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             new VecD(element.X2.Unit?.Value ?? 0, element.Y2.Unit?.Value ?? 0));
     }
 
-    private PathVectorData AddPath(SvgPath element)
+    private PathVectorData AddPath(SvgPath element, StyleContext styleContext)
     {
         VectorPath? path = null;
         if (element.PathData.Unit != null)
@@ -207,13 +215,17 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             };
         }
 
-        StrokeCap strokeLineCap = StrokeCap.Round;
-        StrokeJoin strokeLineJoin = StrokeJoin.Round;
+        StrokeCap strokeLineCap = StrokeCap.Butt;
+        StrokeJoin strokeLineJoin = StrokeJoin.Miter;
+
+        if (styleContext.StrokeLineCap.Unit != null)
+        {
+            strokeLineCap = (StrokeCap)(styleContext.StrokeLineCap.Unit?.Value ?? SvgStrokeLineCap.Butt);
+        }
 
-        if (element.StrokeLineCap.Unit != null)
+        if (styleContext.StrokeLineJoin.Unit != null)
         {
-            strokeLineCap = (StrokeCap)(element.StrokeLineCap.Unit?.Value ?? SvgStrokeLineCap.Butt);
-            strokeLineJoin = (StrokeJoin)(element.StrokeLineJoin.Unit?.Value ?? SvgStrokeLineJoin.Miter);
+            strokeLineJoin = (StrokeJoin)(styleContext.StrokeLineJoin.Unit?.Value ?? SvgStrokeLineJoin.Miter);
         }
 
         return new PathVectorData(path) { StrokeLineCap = strokeLineCap, StrokeLineJoin = strokeLineJoin, };
@@ -228,11 +240,26 @@ internal class SvgDocumentBuilder : IDocumentBuilder
 
     private TextVectorData AddText(SvgText element)
     {
+        Font font = element.FontFamily.Unit.HasValue ? Font.FromFamilyName(element.FontFamily.Unit.Value.Value) : Font.CreateDefault();
+        FontFamilyName? missingFont = null;
+        if(font == null)
+        {
+            font = Font.CreateDefault();
+            missingFont = new FontFamilyName(element.FontFamily.Unit.Value.Value);
+        }
+
+        font.Size = element.FontSize.Unit?.Value ?? 12;
+        font.Bold = element.FontWeight.Unit?.Value == SvgFontWeight.Bold;
+        font.Italic = element.FontStyle.Unit?.Value == SvgFontStyle.Italic;
+
         return new TextVectorData(element.Text.Unit.Value.Value)
         {
             Position = new VecD(
                 element.X.Unit?.Value ?? 0,
-                element.Y.Unit?.Value ?? 0)
+                element.Y.Unit?.Value ?? 0),
+            Font = font,
+            MissingFontFamily = missingFont,
+            MissingFontText = "MISSING_FONT"
         };
     }
 

+ 52 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -18,6 +18,7 @@ using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Vector;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Helpers;
@@ -160,6 +161,10 @@ internal partial class DocumentViewModel
         {
             elementToAdd = AddVectorPath(shapeData);
         }
+        else if (vectorNode.ShapeData is IReadOnlyTextData textData)
+        {
+            elementToAdd = AddText(textData);
+        }
 
         IReadOnlyShapeVectorData data = vectorNode.ShapeData;
 
@@ -178,6 +183,13 @@ internal partial class DocumentViewModel
 
             primitive.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(data.StrokeWidth);
         }
+        else if (elementToAdd is SvgGroup group)
+        {
+            Matrix3X3 transform = data.TransformationMatrix;
+
+            transform = transform.PostConcat(Matrix3X3.CreateScale((float)resizeFactor.X, (float)resizeFactor.Y));
+            group.Transform.Unit = new SvgTransformUnit?(new SvgTransformUnit(transform));
+        }
 
         if (elementToAdd != null)
         {
@@ -276,6 +288,46 @@ internal partial class DocumentViewModel
         elementContainer.Children.Add(image);
     }
 
+    private SvgElement AddText(IReadOnlyTextData textData)
+    {
+        RichText rt = new RichText(textData.Text);
+        rt.Spacing = textData.Spacing;
+        rt.MaxWidth = textData.MaxWidth;
+
+        using Font font = textData.ConstructFont();
+
+        if (rt.Lines.Length <= 1)
+        {
+            return BuildTextElement(textData, textData.Text, font);
+        }
+
+        SvgGroup group = new SvgGroup();
+        for (int i = 0; i < rt.Lines.Length; i++)
+        {
+            var offset = rt.GetLineOffset(i, font);
+
+            var text = BuildTextElement(textData, rt.Lines[i], font);
+            text.Y.Unit = SvgNumericUnit.FromUserUnits(textData.Position.Y + offset.Y);
+
+            group.Children.Add(text);
+        }
+
+        return group;
+    }
+
+    private static SvgText BuildTextElement(IReadOnlyTextData textData, string value, Font font)
+    {
+        SvgText text = new SvgText();
+        text.Text.Unit = new SvgStringUnit(value);
+        text.X.Unit = SvgNumericUnit.FromUserUnits(textData.Position.X);
+        text.Y.Unit = SvgNumericUnit.FromUserUnits(textData.Position.Y);
+        text.FontSize.Unit = SvgNumericUnit.FromUserUnits(font.Size);
+        text.FontFamily.Unit = new SvgStringUnit(font.Family.Name);
+        text.FontWeight.Unit = new SvgEnumUnit<SvgFontWeight>(font.Bold ? SvgFontWeight.Bold : SvgFontWeight.Normal);
+        text.FontStyle.Unit = new SvgEnumUnit<SvgFontStyle>(font.Italic ? SvgFontStyle.Italic : SvgFontStyle.Normal);
+
+        return text;
+    }
 
     private static SvgImage CreateImageElement(VecD resizeFactor, RectD tightBounds,
         Image toSerialize, bool useNearestNeighborForImageUpscaling)

+ 15 - 6
src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs

@@ -239,15 +239,24 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             return;
         }
 
-        DocumentViewModel document = DocumentViewModel.Build(docBuilder => builder.Build(docBuilder, path));
-        AddDocumentViewModelToTheSystem(document);
+        try
+        {
+            DocumentViewModel document = DocumentViewModel.Build(docBuilder => builder.Build(docBuilder, path));
+            AddDocumentViewModelToTheSystem(document);
 
-        if (associatePath)
+            if (associatePath)
+            {
+                document.FullFilePath = path;
+            }
+
+            AddRecentlyOpened(document.FullFilePath);
+        }
+        catch (Exception ex)
         {
-            document.FullFilePath = path;
+            NoticeDialog.Show("FAILED_TO_OPEN_FILE", "ERROR");
+            Console.WriteLine(ex);
+            CrashHelper.SendExceptionInfo(ex);
         }
-
-        AddRecentlyOpened(document.FullFilePath);
     }
 
     /// <summary>