Implemented gathering document hierarchy structure

This commit is contained in:
MarcinZiabek
2022-11-20 18:27:34 +01:00
parent baa0feb5e6
commit d62cec4f66
10 changed files with 272 additions and 15 deletions
+6
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
}
@@ -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() + "...";
}
}
}
}
@@ -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; }
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,