Pārlūkot izejas kodu

Merge pull request #951 from PixiEditor/fixes/24.05.2025

Fixes/24.05.2025
Krzysztof Krysiński 2 mēneši atpakaļ
vecāks
revīzija
6c202cb651

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs

@@ -164,10 +164,11 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
         IReadOnlyDictionary<string, object> data, List<IChangeInfo> infos)
     {
         base.DeserializeAdditionalData(target, data, infos);
-        EmbeddedShapeData = (ShapeVectorData)data["ShapeData"];
+        EmbeddedShapeData = data["ShapeData"] as ShapeVectorData;
 
         if (EmbeddedShapeData == null)
         {
+            Console.WriteLine("Failed to deserialize shape data");
             return;
         }
 

+ 3 - 1
src/PixiEditor.Extensions.CommonApi/Utilities/ByteWriter.cs

@@ -1,3 +1,5 @@
+using System.Text;
+
 namespace PixiEditor.Extensions.CommonApi.Utilities;
 
 public class ByteWriter
@@ -19,8 +21,8 @@ public class ByteWriter
 
     public void WriteString(string value)
     {
+        WriteInt(Encoding.UTF8.GetByteCount(value));
         byte[] stringBytes = System.Text.Encoding.UTF8.GetBytes(value);
-        WriteInt(stringBytes.Length);
         _buffer.AddRange(stringBytes);
     }
 

+ 17 - 8
src/PixiEditor/Models/Serialization/Factories/ByteExtractor.cs

@@ -86,14 +86,10 @@ public class ByteExtractor
     public string GetString()
     {
         int length = GetInt();
-        StringBuilder builder = new StringBuilder();
-        
-        for (int i = 0; i < length; i++)
-        {
-            builder.Append((char)GetInt());
-        }
-        
-        return builder.ToString();
+        string value = Encoding.UTF8.GetString(_data, Position, length);
+
+        Position += length;
+        return value;
     }
 
     public float GetFloat()
@@ -113,4 +109,17 @@ public class ByteExtractor
         
         return value;
     }
+
+    internal string GetStringLegacyDontUse()
+    {
+        int length = GetInt();
+
+        StringBuilder sb = new StringBuilder(length);
+        for (int i = 0; i < length; i++)
+        {
+            sb.Append((char)GetInt());
+        }
+
+        return sb.ToString();
+    }
 }

+ 3 - 1
src/PixiEditor/Models/Serialization/Factories/FontFamilySerializationFactory.cs

@@ -36,7 +36,9 @@ public class FontFamilySerializationFactory : SerializationFactory<byte[], FontF
         }
 
         ByteExtractor extractor = new ByteExtractor(bytes);
-        string fontFamily = extractor.GetString();
+
+        string fontFamily = DeserializeStringCompatible(extractor, serializerData);
+
         bool isFontFromFile = extractor.GetBool();
         string fontPath = null;
         if (isFontFromFile && ResourceLocator != null)

+ 17 - 0
src/PixiEditor/Models/Serialization/Factories/SerializationFactory.cs

@@ -65,6 +65,23 @@ public abstract class SerializationFactory<TSerializable, TOriginal> : Serializa
     {
         return TryDeserialize(rawData, out TOriginal original, serializerData) ? original : default;
     }
+
+    protected string DeserializeStringCompatible(
+        ByteExtractor extractor,
+        (string serializerName, string serializerVersion) serializerData)
+    {
+        if (serializerData.serializerName != "PixiEditor")
+        {
+            return extractor.GetString();
+        }
+
+        if (IsFilePreVersion(serializerData, new Version(2, 0, 0, 87)))
+        {
+            return extractor.GetStringLegacyDontUse();
+        }
+
+        return extractor.GetString();
+    }
     
     public override Type OriginalType => typeof(TOriginal);
 }

+ 4 - 3
src/PixiEditor/Models/Serialization/Factories/TextSerializationFactory.cs

@@ -46,10 +46,11 @@ internal class TextSerializationFactory : VectorShapeSerializationFactory<TextVe
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out TextVectorData original)
     {
-        string text = extractor.GetString();
+        string text = DeserializeStringCompatible(extractor, serializerData);
+
         VecD position = extractor.GetVecD();
         bool antiAlias = extractor.GetBool();
-        string fontFamily = extractor.GetString();
+        string fontFamily = DeserializeStringCompatible(extractor, serializerData);
         bool isFontFromFile = extractor.GetBool();
         string fontPath = null;
         if (isFontFromFile && ResourceLocator != null)
@@ -67,7 +68,7 @@ internal class TextSerializationFactory : VectorShapeSerializationFactory<TextVe
         VectorPath path = null;
         if (hasPath)
         {
-            path = VectorPath.FromSvgPath(extractor.GetString());
+            path = VectorPath.FromSvgPath(DeserializeStringCompatible(extractor, serializerData));
         }
 
         FontFamilyName family =

+ 1 - 1
src/PixiEditor/Models/Serialization/Factories/VectorPathSerializationFactory.cs

@@ -51,7 +51,7 @@ internal class VectorPathSerializationFactory : VectorShapeSerializationFactory<
         VectorPath path;
         if (IsOldSerializer(serializerData))
         {
-            string svgPath = extractor.GetString();
+            string svgPath = extractor.GetStringLegacyDontUse();
             path = VectorPath.FromSvgPath(svgPath);
         }
         else

+ 8 - 5
src/PixiEditor/Models/Serialization/Factories/VectorShapeSerializationFactory.cs

@@ -20,7 +20,8 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
 
         foreach (Type type in types)
         {
-            if (type.IsAssignableTo(typeof(IPaintableSerializationFactory)) && type is { IsAbstract: false, IsInterface: false })
+            if (type.IsAssignableTo(typeof(IPaintableSerializationFactory)) &&
+                type is { IsAbstract: false, IsInterface: false })
             {
                 factories.Add((SerializationFactory)Activator.CreateInstance(type));
             }
@@ -61,9 +62,9 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
         ByteExtractor extractor = new ByteExtractor(data);
 
         Matrix3X3 matrix = extractor.GetMatrix3X3();
-        Paintable strokeColor = TryGetPaintable(extractor, fileIsPrePaintables);
+        Paintable strokeColor = TryGetPaintable(extractor, fileIsPrePaintables, serializerData);
         bool fill = TryGetBool(extractor, serializerData);
-        Paintable fillColor = TryGetPaintable(extractor, fileIsPrePaintables);
+        Paintable fillColor = TryGetPaintable(extractor, fileIsPrePaintables, serializerData);
         float strokeWidth;
         // Previous versions of the serializer saved stroke as int, and serializer data didn't exist
         if (string.IsNullOrEmpty(serializerData.serializerVersion) &&
@@ -100,14 +101,16 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
         return extractor.GetBool();
     }
 
-    private Paintable TryGetPaintable(ByteExtractor extractor, bool fileIsPrePaintables)
+    private Paintable TryGetPaintable(ByteExtractor extractor, bool fileIsPrePaintables,
+        (string serializerName, string serializerVersion) serializerData)
     {
         if (fileIsPrePaintables)
         {
             return new ColorPaintable(extractor.GetColor());
         }
 
-        string paintableType = extractor.GetString();
+        string paintableType = DeserializeStringCompatible(extractor, serializerData);
+
         SerializationFactory factory = PaintableFactories.FirstOrDefault(f => f.DeserializationId == paintableType);
         if (factory == null)
         {

+ 1 - 1
src/PixiEditor/ViewModels/Menu/MenuBarViewModel.cs

@@ -66,7 +66,7 @@ internal class MenuBarViewModel : PixiObservableObject
     {
         menuItemBuilders = serviceProvider.GetServices<MenuItemBuilder>().ToArray();
         commandController = controller;
-        BuildMenu(controller);
+        RebuildMenu();
         controller.Commands.CommandAdded += CommandsOnCommandAdded;
     }
 

+ 9 - 0
src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs

@@ -685,6 +685,15 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         var history =
             preferences.GetLocalPreference<List<AutosaveHistorySession>>(PreferencesConstants.AutosaveHistory);
 
+        bool autosaveEnabled = preferences.GetPreference<bool>(
+            PreferencesConstants.AutosaveEnabled,
+            PreferencesConstants.AutosaveEnabledDefault);
+
+        if(!autosaveEnabled)
+        {
+            return;
+        }
+
         // There are no autosave attempts .. but what if the user has just launched pixieditor for the first time,
         // and it unexpectedly closed before auto saving anything. They could've still had some files open, and they won't be reopened in this session
         // I'll say this is by design

+ 19 - 0
tests/PixiEditor.Tests/SerializationTests.cs

@@ -1,3 +1,4 @@
+using Avalonia.Headless.XUnit;
 using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Surfaces.ImageData;
@@ -5,6 +6,7 @@ using Drawie.Skia;
 using DrawiEngine;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changes.NodeGraph;
+using PixiEditor.Models.IO;
 using PixiEditor.Models.Serialization;
 using PixiEditor.Models.Serialization.Factories;
 using PixiEditor.Models.Serialization.Factories.Paintables;
@@ -47,4 +49,21 @@ public class SerializationTests : PixiEditorTest
             Assert.NotNull(factory);
         }
     }
+
+    [AvaloniaTheory]
+    [InlineData("Fibi")]
+    [InlineData("Pond")]
+    [InlineData("SmlPxlCircShadWithMask")]
+    [InlineData("SmallPixelArtCircleShadow")]
+    [InlineData("SmlPxlCircShadWithMaskClipped")]
+    [InlineData("SmlPxlCircShadWithMaskClippedInFolder")]
+    [InlineData("VectorRectangleClippedToCircle")]
+    [InlineData("VectorRectangleClippedToCircleShadowFilter")]
+    [InlineData("VectorRectangleClippedToCircleMasked")]
+    public void TestThatDeserializationOfSampleFilesDoesntThrow(string fileName)
+    {
+        string pixiFile = Path.Combine("TestFiles", "RenderTests", fileName + ".pixi");
+        var document = Importer.ImportDocument(pixiFile);
+        Assert.NotNull(document);
+    }
 }