Prechádzať zdrojové kódy

Implemented gathering document hierarchy structure

MarcinZiabek 3 rokov pred
rodič
commit
d62cec4f66

+ 6 - 0
QuestPDF.sln

@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configurat
 		.editorconfig = .editorconfig
 	EndProjectSection
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleApp", "SimpleApp\SimpleApp.csproj", "{B361D452-80A8-493A-BEA6-AB268E99153E}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -35,5 +37,9 @@ Global
 		{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B361D452-80A8-493A-BEA6-AB268E99153E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B361D452-80A8-493A-BEA6-AB268E99153E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B361D452-80A8-493A-BEA6-AB268E99153E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B361D452-80A8-493A-BEA6-AB268E99153E}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 EndGlobal

+ 27 - 4
QuestPDF/Drawing/DocumentGenerator.cs

@@ -10,6 +10,7 @@ using QuestPDF.Elements.Text.Items;
 using QuestPDF.Fluent;
 using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
+using QuestPDF.Previewer.Inspection;
 
 namespace QuestPDF.Drawing
 {
@@ -51,14 +52,21 @@ namespace QuestPDF.Drawing
             return canvas.Images;
         }
 
-        internal static ICollection<PreviewerPicture> GeneratePreviewerPictures(IDocument document)
+        internal static DocumentPreviewResult GeneratePreviewerPictures(IDocument document)
         {
             var canvas = new SkiaPictureCanvas();
-            RenderDocument(canvas, document);
-            return canvas.Pictures;
+            InspectionElement documentHierarchy = null;
+            
+            RenderDocument(canvas, document, true, x => documentHierarchy = x);
+            
+            return new DocumentPreviewResult
+            {
+                PageSnapshots = canvas.Pictures.ToList(),
+                DocumentHierarchy = documentHierarchy
+            };
         }
         
-        internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
+        internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, bool applyInspection = false, Action<InspectionElement> inspectionResultHandler = null)
             where TCanvas : ICanvas, IRenderingCanvas
         {
             var container = new DocumentContainer();
@@ -74,7 +82,14 @@ namespace QuestPDF.Drawing
 
             var pageContext = new PageContext();
             RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
+            
+            if (applyInspection)
+                ApplyInspection(content);
+            
             RenderPass(pageContext, canvas, content, debuggingState);
+
+            if (applyInspection)
+                inspectionResultHandler(DocumentHierarchyProcessor.ExtractDocumentHierarchy(content));
         }
         
         internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
@@ -174,6 +189,14 @@ namespace QuestPDF.Drawing
             return debuggingState;
         }
         
+        private static void ApplyInspection(Container content)
+        {
+            content.VisitChildren(x =>
+            {
+                x.CreateProxy(y => y is ElementProxy ? y : new InspectionProxy(y));
+            });
+        }
+        
         internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
         {
             if (content == null)

+ 36 - 0
QuestPDF/Drawing/Proxy/InspectionProxy.cs

@@ -0,0 +1,36 @@
+using System.Collections;
+using System.Collections.Generic;
+using QuestPDF.Infrastructure;
+using QuestPDF.Previewer.Inspection;
+
+namespace QuestPDF.Drawing.Proxy
+{
+    internal class InspectionProxy : ElementProxy
+    {
+        public Dictionary<int, InspectionStateItem> Statistics { get; set; } = new();
+
+        public InspectionProxy(Element child)
+        {
+            Child = child;
+        }
+
+        internal override void Draw(Size availableSpace)
+        {
+            if (Canvas is SkiaCanvasBase canvas)
+            {
+                var matrix = canvas.Canvas.TotalMatrix;
+
+                var inspectionItem = new InspectionStateItem
+                {
+                    Element = Child,
+                    Position = new Position(matrix.TransX, matrix.TransY),
+                    Size = availableSpace
+                };
+
+                Statistics[PageContext.CurrentPage] = inspectionItem;
+            }
+            
+            base.Draw(availableSpace);
+        }
+    }
+}

+ 144 - 0
QuestPDF/Previewer/Inspection/DocumentHierarchyProcessor.cs

@@ -0,0 +1,144 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using QuestPDF.Drawing.Proxy;
+using QuestPDF.Elements;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Previewer.Inspection;
+
+public static class DocumentHierarchyProcessor
+{
+    internal static InspectionElement ExtractDocumentHierarchy(Element container)
+    {
+        return Traverse(container);
+        
+        InspectionElement? Traverse(Element item)
+        {
+            InspectionElement? result = null;
+            Element currentItem = item;
+            
+            while (true)
+            {
+                if (currentItem is InspectionProxy proxy)
+                {
+                    if (proxy.Child.GetType() == typeof(Container))
+                    {
+                        currentItem = proxy.Child;
+                        continue;
+                    }
+                    
+                    var statistics = GetInspectionElement(proxy);
+
+                    if (statistics == null)
+                        return null;
+                    
+                    if (result == null)
+                    {
+                        result = statistics;
+                    }
+                    else
+                    {
+                        result.Children.Add(statistics);
+                    }
+
+                    currentItem = proxy.Child;
+                }
+                else
+                {
+                    var children = currentItem.GetChildren().ToList();
+
+                    if (children.Count == 0)
+                    {
+                        return result;
+                    }
+                    else if (children.Count == 1)
+                    {
+                        currentItem = children.First();
+                        continue;
+                    }
+                    else
+                    {
+                        children
+                            .Select(Traverse)
+                            .Where(x => x != null)
+                            .ToList()
+                            .ForEach(result.Children.Add);
+
+                        return result;
+                    }
+                }
+            }
+        }
+
+        static InspectionElement? GetInspectionElement(InspectionProxy inspectionProxy)
+        {
+            var locations = inspectionProxy
+                .Statistics
+                .Keys
+                .Select(x =>
+                {
+                    var statistics = inspectionProxy.Statistics[x];
+                    
+                    return new InspectionElementLocation
+                    {
+                        PageNumber = x,
+                        Position = new MyPosition
+                        {
+                            Top = statistics.Position.Y,
+                            Left = statistics.Position.X
+                        },
+                        Size = new Size
+                        {
+                            Width = statistics.Size.Width,
+                            Height = statistics.Size.Height
+                        }
+                    };
+                })
+                .ToList();
+            
+            return new InspectionElement
+            {
+                ElementType = inspectionProxy.Child.GetType().Name,
+                Location = locations,
+                Properties = GetElementConfiguration(inspectionProxy.Child),
+                Children = new List<InspectionElement>()
+            };
+        }
+        
+        static IReadOnlyCollection<DocumentElementProperty> GetElementConfiguration(IElement element)
+        {
+            return element
+                .GetType()
+                .GetProperties()
+                .Select(x => new
+                {
+                    Property = x.Name.PrettifyName(),
+                    Value = x.GetValue(element)
+                })
+                .Where(x => !(x.Value is IElement))
+                .Where(x => x.Value is string || !(x.Value is IEnumerable))
+                .Where(x => !(x.Value is TextStyle))
+                .Select(x => new DocumentElementProperty
+                {
+                    Label = x.Property,
+                    Value = FormatValue(x.Value)
+                })
+                .ToList();
+
+            string FormatValue(object value)
+            {
+                const int maxLength = 100;
+                
+                var text = value?.ToString() ?? "-";
+
+                if (text.Length < maxLength)
+                    return text;
+
+                return text.AsSpan(0, maxLength).ToString() + "...";
+            }
+        }
+    }
+}

+ 10 - 0
QuestPDF/Previewer/Inspection/DocumentPreviewResult.cs

@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using QuestPDF.Drawing;
+
+namespace QuestPDF.Previewer.Inspection;
+
+internal class DocumentPreviewResult
+{
+    public IList<PreviewerPicture> PageSnapshots { get; set; }
+    public InspectionElement DocumentHierarchy { get; set; }
+}

+ 33 - 0
QuestPDF/Previewer/Inspection/InspectionStateItem.cs

@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using QuestPDF.Infrastructure;
+
+namespace QuestPDF.Previewer.Inspection
+{
+    internal class InspectionStateItem
+    {
+        public Element Element { get; internal set; }
+        public Infrastructure.Size Size { get; internal set; }
+        public Position Position { get; internal set; }
+    }
+
+    internal class MyPosition
+    {
+        public float Top { get; set; }
+        public float Left { get; set; }
+    }
+    
+    internal class InspectionElementLocation
+    {
+        public int PageNumber { get; internal set; }
+        public MyPosition Position { get; internal set; }
+        public Size Size { get; internal set; }
+    }
+    
+    internal class InspectionElement
+    {
+        public string ElementType { get; internal set; }
+        public IReadOnlyCollection<InspectionElementLocation> Location { get; internal set; }
+        public IReadOnlyCollection<DocumentElementProperty> Properties { get; internal set; }
+        public ICollection<InspectionElement> Children { get; set; }
+    }
+}

+ 2 - 2
QuestPDF/Previewer/PreviewerExtensions.cs

@@ -47,8 +47,8 @@ namespace QuestPDF.Previewer
             {
                 try
                 {
-                    var pictures = DocumentGenerator.GeneratePreviewerPictures(document);
-                    return previewerService.ShowDocumentPreview(pictures);
+                    var documentPreviewResult = DocumentGenerator.GeneratePreviewerPictures(document);
+                    return previewerService.ShowDocumentPreview(documentPreviewResult);
                 }
                 catch (DocumentLayoutException exception)
                 {

+ 12 - 8
QuestPDF/Previewer/PreviewerService.cs

@@ -9,6 +9,7 @@ using System.Text.Json;
 using System.Threading.Tasks;
 using QuestPDF.Drawing;
 using QuestPDF.Drawing.Exceptions;
+using QuestPDF.Previewer.Inspection;
 
 namespace QuestPDF.Previewer
 {
@@ -36,7 +37,8 @@ namespace QuestPDF.Previewer
     internal sealed class UpdateDocumentPreviewApiRequest
     {
         public ICollection<PageSnapshot> PageSnapshots { get; set; }
-
+        public InspectionElement DocumentHierarchy { get; set; }
+        
         public class PageSnapshot
         {
             public string ResourceId { get; } = Guid.NewGuid().ToString("N");
@@ -125,29 +127,31 @@ namespace QuestPDF.Previewer
             }
         }
         
-        public async Task ShowDocumentPreview(ICollection<PreviewerPicture> pictures)
+        public async Task ShowDocumentPreview(DocumentPreviewResult documentPreviewResult)
         {
             using var multipartContent = new MultipartFormDataContent();
             
             var pages = new List<UpdateDocumentPreviewApiRequest.PageSnapshot>();
             
-            foreach (var picture in pictures)
+            foreach (var snapshot in documentPreviewResult.PageSnapshots)
             {
                 var page = new UpdateDocumentPreviewApiRequest.PageSnapshot
                 {
-                    Width = picture.Size.Width,
-                    Height = picture.Size.Height
+                    PageNumber = documentPreviewResult.PageSnapshots.IndexOf(snapshot) + 1,
+                    Width = snapshot.Size.Width,
+                    Height = snapshot.Size.Height
                 };
                 
                 pages.Add(page);
             
-                var pictureStream = picture.Picture.Serialize().AsStream();
+                var pictureStream = snapshot.Picture.Serialize().AsStream();
                 multipartContent.Add(new StreamContent(pictureStream), "snapshots", page.ResourceId);
             }
             
             var request = new UpdateDocumentPreviewApiRequest
             {
-                PageSnapshots = pages
+                PageSnapshots = pages,
+                DocumentHierarchy = documentPreviewResult.DocumentHierarchy
             };
             
             var json = JsonSerializer.Serialize(request);
@@ -157,7 +161,7 @@ namespace QuestPDF.Previewer
             using var response = await HttpClient.PostAsync("/v1/update/preview/document", multipartContent);
             response.EnsureSuccessStatusCode();
             
-            foreach (var picture in pictures)
+            foreach (var picture in documentPreviewResult.PageSnapshots)
                 picture.Picture.Dispose();
         }
         

+ 1 - 0
QuestPDF/QuestPDF.csproj

@@ -18,6 +18,7 @@
         <PackageLicenseExpression>MIT</PackageLicenseExpression>
         <Nullable>enable</Nullable>
         <TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0;net6.0</TargetFrameworks>
+        <TargetFrameworks>net6.0</TargetFrameworks>
         <IncludeSymbols>true</IncludeSymbols>
         <SymbolPackageFormat>snupkg</SymbolPackageFormat>
     </PropertyGroup>

+ 1 - 1
QuestPDF/Settings.cs

@@ -25,7 +25,7 @@
         /// </summary>
         /// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
         public static bool EnableDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
-        
+
         /// <summary>
         /// This flag enables checking the font glyph availability.
         /// If your text contains glyphs that are not present in the specified font,