Explorar o código

Added font document builder

Krzysztof Krysiński hai 7 meses
pai
achega
dd27f3ccca

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 281fa63eab9eb1eb20f9d3025278e43f3b11bd37
+Subproject commit 659194d8041c4f36e45483da323d3e5e338218cb

+ 3 - 0
src/PixiEditor/Helpers/ServiceCollectionHelpers.cs

@@ -110,10 +110,13 @@ internal static class ServiceCollectionHelpers
             .AddSingleton<IoFileType, GifFileType>()
             .AddSingleton<IoFileType, Mp4FileType>()
             .AddSingleton<IoFileType, SvgFileType>()
+            .AddSingleton<IoFileType, TtfFileType>()
+            .AddSingleton<IoFileType, OtfFileType>()
             // Serialization Factories
             .AddAssemblyTypes<SerializationFactory>()
             // Custom document builders
             .AddSingleton<IDocumentBuilder, SvgDocumentBuilder>()
+            .AddSingleton<IDocumentBuilder, FontDocumentBuilder>()
             // Palette Parsers
             .AddSingleton<IPalettesProvider, PaletteProvider>()
             .AddSingleton<PaletteFileParser, JascFileParser>()

+ 2 - 2
src/PixiEditor/Helpers/SupportedFilesHelper.cs

@@ -70,7 +70,7 @@ internal class SupportedFilesHelper
 
     public static List<FilePickerFileType> BuildSaveFilter(FileTypeDialogDataSet.SetKind setKind = FileTypeDialogDataSet.SetKind.Any)
     {
-        var allSupportedExtensions = GetAllSupportedFileTypes(setKind);
+        var allSupportedExtensions = GetAllSupportedFileTypes(setKind).Where(x => x.CanSave).ToList();
         var filter = allSupportedExtensions.Select(i => i.SaveFilter).ToList();
 
         return filter;
@@ -84,7 +84,7 @@ internal class SupportedFilesHelper
             return null;
 
         string extension = Path.GetExtension(file.Path.LocalPath);
-        return allSupportedExtensions.Single(i => i.Extensions.Contains(extension));
+        return allSupportedExtensions.Single(i => i.CanSave && i.Extensions.Contains(extension));
     }
 
     public static List<FilePickerFileType> BuildOpenFilter()

+ 1 - 1
src/PixiEditor/Models/Controllers/FontDomain.cs → src/PixiEditor/Models/Controllers/FontLibrary.cs

@@ -4,7 +4,7 @@ using Drawie.Backend.Core.Text;
 
 namespace PixiEditor.Models.Controllers;
 
-public static class FontDomain
+public static class FontLibrary
 {
     private static List<FontFamilyName> _customFonts = new List<FontFamilyName>();
     private static List<FontFamilyName> _allFonts = new List<FontFamilyName>();

+ 9 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs

@@ -56,6 +56,15 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
         {
             document.TextOverlayHandler.Show(textData.Text, textData.Position, textData.Font,
                 textData.TransformationMatrix, textData.Spacing);
+
+            toolbar.Fill = textData.Fill;
+            toolbar.FillColor = textData.FillColor.ToColor();
+            toolbar.StrokeColor = textData.StrokeColor.ToColor();
+            toolbar.ToolSize = textData.StrokeWidth;
+            toolbar.FontFamily = textData.Font.Family;
+            toolbar.FontSize = textData.Font.Size;
+            toolbar.Spacing = textData.Spacing ?? textData.Font.Size;
+
             lastText = textData.Text;
             position = textData.Position;
             lastMatrix = textData.TransformationMatrix;

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

@@ -39,6 +39,8 @@ internal abstract class IoFileType
     {
         get { return Extensions.Select(GetExtensionFormattedForDialog).ToList(); }
     }
+
+    public virtual bool CanSave => true;
     
     string GetExtensionFormattedForDialog(string extension)
     {

+ 18 - 0
src/PixiEditor/Models/Files/OtfFileType.cs

@@ -0,0 +1,18 @@
+using PixiEditor.Models.IO;
+using PixiEditor.ViewModels.Document;
+
+namespace PixiEditor.Models.Files;
+
+internal class OtfFileType : IoFileType
+{
+    public override string[] Extensions { get; } = new[] { ".otf" };
+    public override string DisplayName { get; } = "OpenType Font";
+    public override FileTypeDialogDataSet.SetKind SetKind { get; } = FileTypeDialogDataSet.SetKind.Vector;
+
+    public override bool CanSave => false;
+
+    public override Task<SaveResult> TrySave(string pathWithExtension, DocumentViewModel document, ExportConfig config, ExportJob? job)
+    {
+        throw new NotSupportedException("Saving OTF files is not supported.");
+    }
+}

+ 18 - 0
src/PixiEditor/Models/Files/TtfFileType.cs

@@ -0,0 +1,18 @@
+using PixiEditor.Models.IO;
+using PixiEditor.ViewModels.Document;
+
+namespace PixiEditor.Models.Files;
+
+internal class TtfFileType : IoFileType
+{
+    public override string[] Extensions { get; } = new[] { ".ttf" };
+    public override string DisplayName { get; } = "TrueType Font";
+    public override FileTypeDialogDataSet.SetKind SetKind { get; } = FileTypeDialogDataSet.SetKind.Vector;
+
+    public override bool CanSave => false;
+
+    public override Task<SaveResult> TrySave(string pathWithExtension, DocumentViewModel document, ExportConfig config, ExportJob? job)
+    {
+        throw new NotSupportedException("Saving TTF files is not supported.");
+    }
+}

+ 66 - 0
src/PixiEditor/Models/IO/CustomDocumentFormats/FontDocumentBuilder.cs

@@ -0,0 +1,66 @@
+using System.Text;
+using Drawie.Backend.Core.Text;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
+
+namespace PixiEditor.Models.IO.CustomDocumentFormats;
+
+internal class FontDocumentBuilder : IDocumentBuilder
+{
+    public IReadOnlyCollection<string> Extensions { get; } = [".ttf", ".otf"];
+
+    public void Build(DocumentViewModelBuilder builder, string path)
+    {
+        Font font = Font.FromFontFamily(new FontFamilyName(new Uri(path), Path.GetFileNameWithoutExtension(path)));
+        font.Size = 12;
+
+        List<char> glyphs = new();
+        int lastGlyph = 0;
+        for (int i = 0; i < char.MaxValue; i++)
+        {
+            if (font.ContainsGlyph(i) && font.MeasureText(((char)i).ToString()) > 0)
+            {
+                lastGlyph++;
+                glyphs.Add((char)i);
+                if (lastGlyph >= font.GlyphCount - 1)
+                {
+                    break;
+                }
+            }
+        }
+
+        int rows = (int)Math.Ceiling(Math.Sqrt(glyphs.Count));
+        int cols = (int)Math.Ceiling((double)glyphs.Count / rows);
+
+        StringBuilder sb = new();
+        for (int i = 0; i < glyphs.Count; i++)
+        {
+            sb.Append(glyphs[i]);
+            if (i % cols == cols - 1)
+            {
+                sb.Append('\n');
+            }
+        }
+
+        TextVectorData textData = new() { Text = sb.ToString(), Font = font, StrokeWidth = 0, Spacing = 12 };
+        RectD bounds = textData.GeometryAABB;
+
+        const int padding = 1;
+
+        FontLibrary.TryAddCustomFont(font.Family);
+
+        textData.Position = new VecD(0, font.Size);
+
+        builder.WithSize((int)Math.Ceiling(bounds.Width) + padding, (int)Math.Ceiling(bounds.Height) + padding)
+            .WithGraph(graph =>
+            {
+                graph.WithNodeOfType<VectorLayerNode>(out int id)
+                    .WithName(font.Family.Name)
+                    .WithAdditionalData(new Dictionary<string, object>() { { "ShapeData", textData } });
+                graph.WithOutputNode(id, "Output");
+            });
+    }
+}

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

@@ -73,7 +73,7 @@ internal class TextSerializationFactory : VectorShapeSerializationFactory<TextVe
         }
         else if (isFontFromFile)
         {
-            FontDomain.TryAddCustomFont(family);
+            FontLibrary.TryAddCustomFont(family);
         }
 
         font.Size = fontSize;

+ 3 - 3
src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/FontFamilySettingViewModel.cs

@@ -52,8 +52,8 @@ internal class FontFamilySettingViewModel : Setting<FontFamilyName>
     public FontFamilySettingViewModel(string name, string displayName) : base(name)
     {
         Label = displayName;
-        Fonts = new ObservableCollection<FontFamilyName>(FontDomain.AllFonts);
-        FontDomain.FontAdded += (font) => Fonts.Add(font); 
+        Fonts = new ObservableCollection<FontFamilyName>(FontLibrary.AllFonts);
+        FontLibrary.FontAdded += (font) => Fonts.Add(font); 
         UploadFontCommand = new AsyncRelayCommand(UploadFont);
     }
 
@@ -76,7 +76,7 @@ internal class FontFamilySettingViewModel : Setting<FontFamilyName>
 
             var fontPath = dialog[0];
             FontFamilyName familyName = new FontFamilyName(fontPath.Path, Path.GetFileNameWithoutExtension(fontPath.Name));
-            FontDomain.TryAddCustomFont(familyName);
+            FontLibrary.TryAddCustomFont(familyName);
             
             FontIndex = Fonts.IndexOf(familyName);
         }

+ 8 - 2
src/PixiEditor/ViewModels/Tools/ToolSettings/Toolbars/TextToolbar.cs

@@ -16,7 +16,13 @@ internal class TextToolbar : FillableShapeToolbar, ITextToolbar
         }
         set
         {
-            GetSetting<FontFamilySettingViewModel>(nameof(FontFamily)).Value = value;
+            int index = Array.IndexOf(FontLibrary.AllFonts, value);
+            if (index == -1)
+            {
+                index = 0;
+            }
+
+            GetSetting<FontFamilySettingViewModel>(nameof(FontFamily)).FontIndex = index;
         }
     }
 
@@ -59,7 +65,7 @@ internal class TextToolbar : FillableShapeToolbar, ITextToolbar
     public TextToolbar()
     {
         AddSetting(new FontFamilySettingViewModel(nameof(FontFamily), "FONT_LABEL"));
-        FontFamily = FontDomain.DefaultFontFamily;
+        FontFamily = FontLibrary.DefaultFontFamily;
         
         var sizeSetting =
             new SizeSettingViewModel(nameof(FontSize), "FONT_SIZE_LABEL", unit: new LocalizedString("UNIT_PT"))

+ 11 - 2
src/PixiEditor/Views/Overlays/TextOverlay/TextOverlay.cs

@@ -135,7 +135,8 @@ internal class TextOverlay : Overlay
     {
         shortcuts = new Dictionary<KeyCombination, Action>
         {
-            { new KeyCombination(Key.C, KeyModifiers.Control), CopyText },
+            { new KeyCombination(Key.C, KeyModifiers.Control), () => CopyText() },
+            { new KeyCombination(Key.C, KeyModifiers.Control | KeyModifiers.Shift), () => CopyText(true) },
             { new KeyCombination(Key.X, KeyModifiers.Control), CutText },
             { new KeyCombination(Key.V, KeyModifiers.Control), PasteText },
             { new KeyCombination(Key.Delete, KeyModifiers.None), () => DeleteChar(0) },
@@ -394,12 +395,18 @@ internal class TextOverlay : Overlay
         SelectionEnd = end + 1;
     }
 
-    private void CopyText()
+    private void CopyText(bool asUnicode = false)
     {
         if (CursorPosition == SelectionEnd) return;
         string selectedText = Text.Substring(
             Math.Min(CursorPosition, SelectionEnd),
             Math.Abs(CursorPosition - SelectionEnd));
+
+        if (asUnicode)
+        {
+            selectedText = string.Join(" ", selectedText.Select(c => $"U+{((int)c):X4}"));
+        }
+
         ClipboardController.Clipboard.SetTextAsync(selectedText);
     }
 
@@ -703,6 +710,8 @@ internal class TextOverlay : Overlay
         if (textOverlay == null) return newPos;
         if (textOverlay.Text == null) return 0;
 
+        if (textOverlay.glyphPositions == null) return 0;
+
         return Math.Clamp(newPos, 0, textOverlay.glyphPositions.Length - 1);
     }
 }