Changed previewer communication layer to http
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
[assembly: InternalsVisibleTo("QuestPDF.Previewer")]
|
||||
[assembly: InternalsVisibleTo("QuestPDF.UnitTests")]
|
||||
[assembly: InternalsVisibleTo("QuestPDF.Examples")]
|
||||
[assembly: InternalsVisibleTo("QuestPDF.ReportSample")]
|
||||
@@ -51,6 +51,13 @@ namespace QuestPDF.Drawing
|
||||
return canvas.Images;
|
||||
}
|
||||
|
||||
internal static ICollection<PreviewerPicture> GeneratePreviewerPictures(IDocument document)
|
||||
{
|
||||
var canvas = new SkiaPictureCanvas();
|
||||
RenderDocument(canvas, document);
|
||||
return canvas.Pictures;
|
||||
}
|
||||
|
||||
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
public class PreviewerPicture
|
||||
{
|
||||
public SKPicture Picture { get; set; }
|
||||
public Size Size { get; set; }
|
||||
|
||||
public PreviewerPicture(SKPicture picture, Size size)
|
||||
{
|
||||
Picture = picture;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SkiaPictureCanvas : SkiaCanvasBase
|
||||
{
|
||||
private SKPictureRecorder? PictureRecorder { get; set; }
|
||||
private Size? CurrentPageSize { get; set; }
|
||||
|
||||
public ICollection<PreviewerPicture> Pictures { get; } = new List<PreviewerPicture>();
|
||||
|
||||
public override void BeginDocument()
|
||||
{
|
||||
Pictures.Clear();
|
||||
}
|
||||
|
||||
public override void BeginPage(Size size)
|
||||
{
|
||||
CurrentPageSize = size;
|
||||
PictureRecorder = new SKPictureRecorder();
|
||||
|
||||
Canvas = PictureRecorder.BeginRecording(new SKRect(0, 0, size.Width, size.Height));
|
||||
}
|
||||
|
||||
public override void EndPage()
|
||||
{
|
||||
var picture = PictureRecorder?.EndRecording();
|
||||
|
||||
if (picture != null && CurrentPageSize.HasValue)
|
||||
Pictures.Add(new PreviewerPicture(picture, CurrentPageSize.Value));
|
||||
|
||||
PictureRecorder?.Dispose();
|
||||
PictureRecorder = null;
|
||||
}
|
||||
|
||||
public override void EndDocument() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
public class ExceptionDocument : IDocument
|
||||
{
|
||||
private Exception Exception { get; }
|
||||
|
||||
public ExceptionDocument(Exception exception)
|
||||
{
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public DocumentMetadata GetMetadata()
|
||||
{
|
||||
return DocumentMetadata.Default;
|
||||
}
|
||||
|
||||
public void Compose(IDocumentContainer document)
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(50);
|
||||
page.DefaultTextStyle(x => x.FontSize(16));
|
||||
|
||||
page.Foreground().PaddingTop(5).Border(10).BorderColor(Colors.Red.Medium);
|
||||
|
||||
page.Header()
|
||||
.ShowOnce()
|
||||
.PaddingBottom(5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.Spacing(15);
|
||||
|
||||
row.AutoItem()
|
||||
.PaddingTop(15)
|
||||
.Width(48)
|
||||
.AspectRatio(1)
|
||||
.Canvas((canvas, size) =>
|
||||
{
|
||||
const float iconSize = 24;
|
||||
using var iconPath = SKPath.ParseSvgPathData("M23,12L20.56,14.78L20.9,18.46L17.29,19.28L15.4,22.46L12,21L8.6,22.47L6.71,19.29L3.1,18.47L3.44,14.78L1,12L3.44,9.21L3.1,5.53L6.71,4.72L8.6,1.54L12,3L15.4,1.54L17.29,4.72L20.9,5.54L20.56,9.22L23,12M20.33,12L18.5,9.89L18.74,7.1L16,6.5L14.58,4.07L12,5.18L9.42,4.07L8,6.5L5.26,7.09L5.5,9.88L3.67,12L5.5,14.1L5.26,16.9L8,17.5L9.42,19.93L12,18.81L14.58,19.92L16,17.5L18.74,16.89L18.5,14.1L20.33,12M11,15H13V17H11V15M11,7H13V13H11V7");
|
||||
|
||||
using var paint = new SKPaint()
|
||||
{
|
||||
Color = SKColors.Red,
|
||||
IsAntialias = true
|
||||
};
|
||||
|
||||
canvas.Scale(size.Width / iconSize);
|
||||
canvas.DrawPath(iconPath, paint);
|
||||
});
|
||||
|
||||
row.RelativeItem()
|
||||
.Column(column =>
|
||||
{
|
||||
column.Item().Text("Exception").FontSize(36).FontColor(Colors.Red.Medium).Bold();
|
||||
column.Item().PaddingTop(-10).Text("Don't panic! Just analyze what's happened...").FontSize(18).FontColor(Colors.Red.Medium).Bold();
|
||||
});
|
||||
});
|
||||
|
||||
page.Content().PaddingVertical(20).Column(column =>
|
||||
{
|
||||
var currentException = Exception;
|
||||
|
||||
while (currentException != null)
|
||||
{
|
||||
column.Item()
|
||||
.PaddingTop(25)
|
||||
.PaddingBottom(15)
|
||||
|
||||
.Padding(-10)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(x => x.FontSize(18));
|
||||
|
||||
text.Span(currentException.GetType().Name + ": ").Bold();
|
||||
text.Span(currentException.Message);
|
||||
});
|
||||
|
||||
foreach (var trace in currentException.StackTrace.Split('\n'))
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.ShowEntire()
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Lighten2)
|
||||
.PaddingVertical(5)
|
||||
.Text(trace.Trim())
|
||||
.FontSize(12);
|
||||
}
|
||||
|
||||
currentException = currentException.InnerException;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
|
||||
[assembly: System.Reflection.Metadata.MetadataUpdateHandler(typeof(QuestPDF.Previewer.HotReloadManager))]
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper for subscribing to hot reload notifications.
|
||||
/// </summary>
|
||||
internal static class HotReloadManager
|
||||
{
|
||||
public static event EventHandler? UpdateApplicationRequested;
|
||||
|
||||
public static void UpdateApplication(Type[]? _)
|
||||
{
|
||||
UpdateApplicationRequested?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,46 @@
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static async Task ShowInPreviewer(this IDocument document, int port = 12500)
|
||||
{
|
||||
var previewerService = new PreviewerService(port);
|
||||
await previewerService.Connect();
|
||||
await RefreshPreview();
|
||||
|
||||
HotReloadManager.UpdateApplicationRequested += (_, _) => RefreshPreview();
|
||||
|
||||
while (true)
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
|
||||
Task RefreshPreview()
|
||||
{
|
||||
var pictures = GetPictures();
|
||||
return previewerService.RefreshPreview(pictures);
|
||||
|
||||
ICollection<PreviewerPicture> GetPictures()
|
||||
{
|
||||
try
|
||||
{
|
||||
return DocumentGenerator.GeneratePreviewerPictures(document);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var exceptionDocument = new ExceptionDocument(exception);
|
||||
return DocumentGenerator.GeneratePreviewerPictures(exceptionDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
internal class PreviewerRefreshCommand
|
||||
{
|
||||
public ICollection<Page> Pages { get; set; }
|
||||
|
||||
public class Page
|
||||
{
|
||||
public string Id { get; }
|
||||
|
||||
public float Width { get; init; }
|
||||
public float Height { get; init; }
|
||||
|
||||
public Page()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,132 @@
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using QuestPDF.Drawing;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
internal class PreviewerService
|
||||
{
|
||||
private HttpClient HttpClient { get; init; }
|
||||
|
||||
public PreviewerService(int port)
|
||||
{
|
||||
HttpClient = new()
|
||||
{
|
||||
BaseAddress = new Uri($"http://localhost:{port}/"),
|
||||
Timeout = TimeSpan.FromMilliseconds(100)
|
||||
};
|
||||
}
|
||||
|
||||
public async Task Connect()
|
||||
{
|
||||
var isConnected = await TestConnection();
|
||||
|
||||
if (isConnected)
|
||||
return;
|
||||
|
||||
StartPreviewer();
|
||||
await WaitForConnection();
|
||||
}
|
||||
|
||||
private async Task<bool> TestConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await HttpClient.GetAsync("/ping");
|
||||
return result.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Version> GetPreviewerVersion()
|
||||
{
|
||||
var result = await HttpClient.GetAsync("/version");
|
||||
var value = await result.Content.ReadAsStringAsync();
|
||||
return Version.Parse(value);
|
||||
}
|
||||
|
||||
private static void StartPreviewer()
|
||||
{
|
||||
try
|
||||
{
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
UseShellExecute = false,
|
||||
FileName = "questpdf-test",
|
||||
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'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckVersionCompatibility(Version version)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private async Task WaitForConnection()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
|
||||
var isConnected = await TestConnection();
|
||||
|
||||
if (isConnected)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshPreview(ICollection<PreviewerPicture> pictures)
|
||||
{
|
||||
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");
|
||||
|
||||
await HttpClient.PostAsync("/update/preview", multipartContent);
|
||||
|
||||
foreach (var picture in pictures)
|
||||
picture.Picture.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -4,7 +4,7 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2022.3.1</Version>
|
||||
<Version>2022.4.0-alpha1</Version>
|
||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
|
||||
<LangVersion>9</LangVersion>
|
||||
@@ -24,7 +24,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
|
||||
<InternalsVisibleTo Include="QuestPDF.ReportSample" />
|
||||
<InternalsVisibleTo Include="QuestPDF.UnitTests" />
|
||||
<InternalsVisibleTo Include="QuestPDF.Examples" />
|
||||
<InternalsVisibleTo Include="QuestPDF.Previewer" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
- Added minimal API to help you quickly start with QuestPDF development and speed up prototyping,
|
||||
- Improved exception message when desired font type cannot be found (instead of loading default font on Windows and failing with wrong characters on Linux),
|
||||
- Improved support for custom font types: loading all type faces from a file, respecting true font family, using CSS-like algorithm to find best style match,
|
||||
- Added support for custom page number formats in the `Text` element, e.g. you can implement roman literal style if required,
|
||||
- Improved text API to make it more concise,
|
||||
- Extended support for the `Section` element (previously the `Location` element) by tracking: beginning page number, end page number, page length, page number within location,
|
||||
- Renaming: the `ExternalLink` element was renamed to the `Hyperlink` element,
|
||||
- Renaming: the `Location` element was renamed to the `Section` element,
|
||||
- Renaming: the `InternalLink` element was renamed to the `SectionLink` element,
|
||||
- Updated homepage and GitHub pages content.
|
||||
- Introduced the Previewer tool - a hot-reload powered, cross-platform program that visualizes your PDF document and updates the preview every time you make a code change (available only for dotnet 6),
|
||||
- Improved default word-wrapping algorithm to better handle words which do not fit on the available width,
|
||||
- Introduced new word-wrapping option 'WrapAnywhere' that wraps word at the last possible character instead of moving it into new line.
|
||||
|
||||
Reference in New Issue
Block a user