Previewer: sending details about layout overflow exception
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer;
|
||||
using QuestPDF.ReportSample.Layouts;
|
||||
|
||||
namespace QuestPDF.ReportSample
|
||||
@@ -24,11 +25,13 @@ namespace QuestPDF.ReportSample
|
||||
[Test]
|
||||
public void GenerateAndShowPdf()
|
||||
{
|
||||
Report.ShowInPreviewer();
|
||||
|
||||
//ImagePlaceholder.Solid = true;
|
||||
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
||||
Report.GeneratePdf(path);
|
||||
Process.Start("explorer.exe", path);
|
||||
//
|
||||
// var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
||||
// Report.GeneratePdf(path);
|
||||
// Process.Start("explorer.exe", path);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace QuestPDF.Drawing
|
||||
$"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
|
||||
$"Please analyze your documents structure to detect this element and fix its size constraints.";
|
||||
|
||||
var elementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode.";
|
||||
var elementTrace = debuggingState?.BuildTrace(); // ?? "Debug trace is available only in the DEBUG mode.";
|
||||
|
||||
throw new DocumentLayoutException(message, elementTrace);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using QuestPDF.Previewer;
|
||||
|
||||
namespace QuestPDF.Drawing.Exceptions
|
||||
{
|
||||
public class DocumentLayoutException : Exception
|
||||
{
|
||||
public string? ElementTrace { get; }
|
||||
internal LayoutRenderingTrace? ElementTrace { get; }
|
||||
|
||||
internal DocumentLayoutException(string message, string? elementTrace = null) : base(message)
|
||||
internal DocumentLayoutException(string message, LayoutRenderingTrace? elementTrace = null) : base(message)
|
||||
{
|
||||
ElementTrace = elementTrace;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ using System.Text;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer;
|
||||
using Size = QuestPDF.Infrastructure.Size;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
@@ -57,48 +59,35 @@ namespace QuestPDF.Drawing.Proxy
|
||||
item.SpacePlan = spacePlan;
|
||||
}
|
||||
|
||||
public string BuildTrace()
|
||||
public LayoutRenderingTrace BuildTrace()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var nestingLevel = 0;
|
||||
return Traverse(Root);
|
||||
|
||||
Traverse(Root);
|
||||
return builder.ToString();
|
||||
|
||||
void Traverse(DebugStackItem item)
|
||||
LayoutRenderingTrace Traverse(DebugStackItem item)
|
||||
{
|
||||
var indent = new string(' ', nestingLevel * 4);
|
||||
var title = item.Element.GetType().Name;
|
||||
|
||||
if (item.Element is DebugPointer debugPointer)
|
||||
return new LayoutRenderingTrace
|
||||
{
|
||||
title = debugPointer.Target;
|
||||
title += debugPointer.Highlight ? " 🌟" : string.Empty;
|
||||
}
|
||||
|
||||
if (item.SpacePlan.Type != SpacePlanType.FullRender)
|
||||
title = "🔥 " + title;
|
||||
|
||||
builder.AppendLine(indent + title);
|
||||
builder.AppendLine(indent + new string('-', title.Length));
|
||||
|
||||
builder.AppendLine(indent + "Available space: " + item.AvailableSpace);
|
||||
builder.AppendLine(indent + "Requested space: " + item.SpacePlan);
|
||||
|
||||
foreach (var configuration in GetElementConfiguration(item.Element))
|
||||
builder.AppendLine(indent + configuration);
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
nestingLevel++;
|
||||
item.Stack.ToList().ForEach(Traverse);
|
||||
nestingLevel--;
|
||||
ElementType = item.Element.GetType().Name,
|
||||
ElementProperties = GetElementConfiguration(item.Element).ToList(),
|
||||
AvailableSpace = new QuestPDF.Previewer.Size()
|
||||
{
|
||||
Width = item.AvailableSpace.Width,
|
||||
Height = item.AvailableSpace.Height
|
||||
},
|
||||
SpacePlan = new QuestPDF.Previewer.SpacePlan()
|
||||
{
|
||||
Width = item.SpacePlan.Width,
|
||||
Height = item.SpacePlan.Height,
|
||||
Type = item.SpacePlan.Type
|
||||
},
|
||||
Children = item.Stack.Select(Traverse).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
static IEnumerable<string> GetElementConfiguration(IElement element)
|
||||
static IEnumerable<DocumentElementProperty> GetElementConfiguration(IElement element)
|
||||
{
|
||||
if (element is DebugPointer)
|
||||
return Enumerable.Empty<string>();
|
||||
return Enumerable.Empty<DocumentElementProperty>();
|
||||
|
||||
return element
|
||||
.GetType()
|
||||
@@ -111,7 +100,11 @@ namespace QuestPDF.Drawing.Proxy
|
||||
.Where(x => !(x.Value is IElement))
|
||||
.Where(x => x.Value is string || !(x.Value is IEnumerable))
|
||||
.Where(x => !(x.Value is TextStyle))
|
||||
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
|
||||
.Select(x => new DocumentElementProperty
|
||||
{
|
||||
Label = x.Property,
|
||||
Value = FormatValue(x.Value)
|
||||
});
|
||||
|
||||
string FormatValue(object value)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -16,4 +17,6 @@ internal static class Helpers
|
||||
|
||||
return client.PostAsync(requestUri, jsonContent, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
internal sealed class DocumentElementProperty
|
||||
{
|
||||
public string Label { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class Size
|
||||
{
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class SpacePlan
|
||||
{
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
public SpacePlanType Type { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class LayoutRenderingTrace
|
||||
{
|
||||
public string ElementType { get; set; }
|
||||
public IReadOnlyCollection<DocumentElementProperty> ElementProperties { get; set; }
|
||||
public Size AvailableSpace { get; set; }
|
||||
public SpacePlan SpacePlan { get; set; }
|
||||
public IReadOnlyCollection<LayoutRenderingTrace> Children { get; set; }
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
@@ -16,8 +17,10 @@ namespace QuestPDF.Previewer
|
||||
document.ShowInPreviewerAsync(port).ConfigureAwait(true).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public static async Task ShowInPreviewerAsync(this IDocument document, int port = 5000)
|
||||
public static async Task ShowInPreviewerAsync(this IDocument document, int port = 5000, CancellationToken cancellationToken = default)
|
||||
{
|
||||
QuestPDF.Settings.EnableDebugging = true;
|
||||
|
||||
var previewerService = new PreviewerService(port);
|
||||
|
||||
using var cancellationTokenSource = new CancellationTokenSource();
|
||||
@@ -26,10 +29,19 @@ namespace QuestPDF.Previewer
|
||||
await previewerService.Connect();
|
||||
await RefreshPreview();
|
||||
|
||||
//
|
||||
// //HotReloadManager.UpdateApplicationRequested += (_, _) => RefreshPreview();
|
||||
//
|
||||
// await WaitForPreviewerExit(cancellationTokenSource.Token);
|
||||
#if NET6_0_OR_GREATER
|
||||
HotReloadManager.UpdateApplicationRequested += (_, _) => RefreshPreview();
|
||||
#endif
|
||||
|
||||
while (true)
|
||||
{
|
||||
//if (cancellationToken.IsCancellationRequested)
|
||||
// break;
|
||||
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(1000));
|
||||
}
|
||||
|
||||
//await previewerService.Disconnect();
|
||||
|
||||
Task RefreshPreview()
|
||||
{
|
||||
@@ -38,22 +50,15 @@ namespace QuestPDF.Previewer
|
||||
var pictures = DocumentGenerator.GeneratePreviewerPictures(document);
|
||||
return previewerService.ShowDocumentPreview(pictures);
|
||||
}
|
||||
catch (DocumentLayoutException exception)
|
||||
{
|
||||
return previewerService.ShowLayoutError(exception);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
return previewerService.ShowGenericError(exception);
|
||||
}
|
||||
}
|
||||
//
|
||||
// async Task WaitForPreviewerExit(CancellationToken cancellationToken)
|
||||
// {
|
||||
// while (true)
|
||||
// {
|
||||
// if (cancellationToken.IsCancellationRequested)
|
||||
// return;
|
||||
//
|
||||
// await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
internal class PreviewerRefreshCommand
|
||||
{
|
||||
public ICollection<Page> Pages { get; set; }
|
||||
|
||||
public class Page
|
||||
{
|
||||
public string Id { get; } = Guid.NewGuid().ToString("N");
|
||||
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
@@ -20,10 +22,15 @@ namespace QuestPDF.Previewer
|
||||
public GenericError? InnerException { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class ShowLayoutErrorApiRequestVersion
|
||||
internal sealed class ShowGenericErrorApiRequest
|
||||
{
|
||||
public GenericError? Error { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class ShowLayoutErrorApiRequest
|
||||
{
|
||||
public LayoutRenderingTrace? Trace { get; set; }
|
||||
}
|
||||
|
||||
|
||||
internal sealed class UpdateDocumentPreviewApiRequest
|
||||
@@ -62,6 +69,62 @@ namespace QuestPDF.Previewer
|
||||
};
|
||||
}
|
||||
|
||||
private void StartPreviewer()
|
||||
{
|
||||
try
|
||||
{
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = "questpdf-previewer",
|
||||
Arguments = $"{Port}",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new Exception("Cannot start the QuestPDF Previewer tool. " +
|
||||
"Please install it by executing in terminal the following command: 'dotnet tool install --global QuestPDF.Previewer'.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Connect()
|
||||
{
|
||||
var isAvailable = await IsPreviewerAvailable();
|
||||
|
||||
if (!isAvailable)
|
||||
{
|
||||
//StartPreviewer();
|
||||
//await WaitForConnection();
|
||||
}
|
||||
|
||||
//var previewerVersion = await GetPreviewerVersion();
|
||||
//CheckVersionCompatibility(previewerVersion);
|
||||
}
|
||||
|
||||
public async Task Disconnect()
|
||||
{
|
||||
await HttpClient.GetAsync("/v1/disconnect");
|
||||
}
|
||||
|
||||
private async Task<bool> IsPreviewerAvailable()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var result = await HttpClient.GetAsync("/ping");
|
||||
return result.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ShowDocumentPreview(ICollection<PreviewerPicture> pictures)
|
||||
{
|
||||
using var multipartContent = new MultipartFormDataContent();
|
||||
@@ -100,7 +163,7 @@ namespace QuestPDF.Previewer
|
||||
|
||||
public async Task ShowGenericError(Exception exception)
|
||||
{
|
||||
var payload = new ShowLayoutErrorApiRequestVersion
|
||||
var payload = new ShowGenericErrorApiRequest
|
||||
{
|
||||
Error = MapException(exception)
|
||||
};
|
||||
@@ -122,32 +185,19 @@ namespace QuestPDF.Previewer
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Connect()
|
||||
public async Task ShowLayoutError(DocumentLayoutException exception)
|
||||
{
|
||||
var isAvailable = await IsPreviewerAvailable();
|
||||
|
||||
if (!isAvailable)
|
||||
var payload = new ShowLayoutErrorApiRequest
|
||||
{
|
||||
//StartPreviewer();
|
||||
//await WaitForConnection();
|
||||
}
|
||||
|
||||
//var previewerVersion = await GetPreviewerVersion();
|
||||
//CheckVersionCompatibility(previewerVersion);
|
||||
Trace = exception.ElementTrace
|
||||
};
|
||||
|
||||
await HttpClient.PostAsJsonAsync("/v1/update/error/layout", payload);
|
||||
}
|
||||
|
||||
private async Task<bool> IsPreviewerAvailable()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var result = await HttpClient.GetAsync("/ping");
|
||||
return result.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// private async Task<Version> GetPreviewerVersion()
|
||||
// {
|
||||
@@ -155,36 +205,11 @@ namespace QuestPDF.Previewer
|
||||
// return await result.Content.ReadFromJsonAsync<Version>();
|
||||
// }
|
||||
//
|
||||
// private void StartPreviewer()
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// var process = new Process
|
||||
// {
|
||||
// StartInfo = new()
|
||||
// {
|
||||
// FileName = "questpdf-previewer",
|
||||
// Arguments = $"{Port}",
|
||||
// UseShellExecute = false,
|
||||
// CreateNoWindow = true
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// process.Start();
|
||||
//
|
||||
// Task.Run(async () =>
|
||||
// {
|
||||
// await process.WaitForExitAsync();
|
||||
// process.Dispose();
|
||||
// OnPreviewerStopped?.Invoke();
|
||||
// });
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// throw new Exception("Cannot start the QuestPDF Previewer tool. " +
|
||||
// "Please install it by executing in terminal the following command: 'dotnet tool install --global QuestPDF.Previewer'.");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
// private void CheckVersionCompatibility(Version version)
|
||||
// {
|
||||
@@ -216,39 +241,6 @@ namespace QuestPDF.Previewer
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public async Task RefreshPreview(ICollection<PreviewerPicture> pictures)
|
||||
// {
|
||||
// using var multipartContent = new MultipartFormDataContent();
|
||||
//
|
||||
// var pages = new List<PreviewerRefreshCommand.Page>();
|
||||
//
|
||||
// foreach (var picture in pictures)
|
||||
// {
|
||||
// var page = new PreviewerRefreshCommand.Page
|
||||
// {
|
||||
// Width = picture.Size.Width,
|
||||
// Height = picture.Size.Height
|
||||
// };
|
||||
//
|
||||
// pages.Add(page);
|
||||
//
|
||||
// var pictureStream = picture.Picture.Serialize().AsStream();
|
||||
// multipartContent.Add(new StreamContent(pictureStream), page.Id, page.Id);
|
||||
// }
|
||||
//
|
||||
// var command = new PreviewerRefreshCommand
|
||||
// {
|
||||
// Pages = pages
|
||||
// };
|
||||
//
|
||||
// multipartContent.Add(JsonContent.Create(command), "command");
|
||||
//
|
||||
// using var _ = await HttpClient.PostAsync("/update/preview", multipartContent);
|
||||
//
|
||||
// foreach (var picture in pictures)
|
||||
// picture.Picture.Dispose();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp3.0;</TargetFrameworks>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
Reference in New Issue
Block a user