Implemented gathering document hierarchy structure
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user