Optimize document rendering.

We dont need to render the document two times anymore.
This commit is contained in:
Bebo-Maker
2022-03-17 10:34:00 +01:00
parent d18ee5f282
commit 346513ee85
5 changed files with 141 additions and 89 deletions
@@ -1,55 +0,0 @@
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Skia;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Previewer
{
internal class DocumentRenderOperation : ICustomDrawOperation
{
public IDocument Document { get; }
public float PageSpacing { get; }
public Rect Bounds { get; }
public DocumentRenderOperation(IDocument document, Rect bounds, float pageSpacing)
{
Document = document;
Bounds = bounds;
PageSpacing = pageSpacing;
}
public void Dispose() { }
public bool Equals(ICustomDrawOperation? other)
{
return other is DocumentRenderOperation renderer && renderer.Document == Document;
}
public bool HitTest(Point p)
{
return false;
}
public void Render(IDrawingContextImpl context)
{
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
if (canvas == null)
throw new InvalidOperationException($"Context needs to be ISkiaDrawingContextImpl but got {nameof(context)}");
using (new SKAutoCanvasRestore(canvas))
{
var previewerCanvas = new PreviewerCanvas()
{
Canvas = canvas,
PageSpacing = PageSpacing,
MaxPageWidth = (float)Bounds.Width,
};
DocumentGenerator.RenderDocument(previewerCanvas, Document);
}
}
}
}
+61
View File
@@ -0,0 +1,61 @@
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Previewer
{
internal class DocumentRenderer
{
public float PageSpacing { get; set; }
public Size Bounds { get; private set; }
public IDocument? Document { get; private set; }
public SKPicture? Picture { get; private set; }
public Exception? RenderException { get; private set; }
public bool IsRendering { get; private set; }
public void UpdateDocument(IDocument? document)
{
Document = document;
RenderException = null;
if (document != null)
{
try
{
IsRendering = true;
RenderDocument(document);
}
catch (Exception ex)
{
RenderException = ex;
Picture?.Dispose();
Picture = null;
}
finally
{
IsRendering = false;
}
}
}
private void RenderDocument(IDocument document)
{
Picture?.Dispose();
var canvas = new PreviewerCanvas()
{
PageSpacing = PageSpacing,
};
using var recorder = new SKPictureRecorder();
DocumentGenerator.RenderDocument(canvas, new SizeTrackingCanvas(), document, s =>
{
var width = s.PageSizes.Max(p => p.Width);
var height = s.PageSizes.Sum(p => p.Height) + ((s.PageSizes.Count - 1) * PageSpacing);
Bounds = new Size(width, height);
canvas.Canvas = recorder.BeginRecording(new SKRect(0, 0, width, height));
canvas.MaxPageWidth = width;
});
Picture = recorder.EndRecording();
}
}
}
+35 -33
View File
@@ -1,7 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using QuestPDF.Drawing;
using Avalonia.Threading;
using QuestPDF.Infrastructure;
namespace QuestPDF.Previewer
@@ -9,7 +9,7 @@ namespace QuestPDF.Previewer
internal class PreviewerControl : Control
{
public static readonly StyledProperty<IDocument?> DocumentProperty =
AvaloniaProperty.Register<PreviewerControl, IDocument?>(nameof(Document));
AvaloniaProperty.Register<PreviewerControl, IDocument?>(nameof(Document));
public IDocument? Document
{
@@ -18,7 +18,7 @@ namespace QuestPDF.Previewer
}
public static readonly StyledProperty<bool> IsGeneratingDocumentProperty =
AvaloniaProperty.Register<PreviewerControl, bool>(nameof(IsGeneratingDocument));
AvaloniaProperty.Register<PreviewerControl, bool>(nameof(IsGeneratingDocument));
public bool IsGeneratingDocument
{
@@ -26,51 +26,53 @@ namespace QuestPDF.Previewer
set => SetValue(IsGeneratingDocumentProperty, value);
}
public float PageSpacing { get; set; } = 20;
public static readonly StyledProperty<double> PageSpacingProperty =
AvaloniaProperty.Register<PreviewerControl, double>(nameof(PageSpacing), 20);
public double PageSpacing
{
get => GetValue(PageSpacingProperty);
set => SetValue(PageSpacingProperty, value);
}
private readonly DocumentRenderer _renderer = new();
public PreviewerControl()
{
DocumentProperty
.Changed
.Subscribe(_ => InvalidateVisual());
_renderer.PageSpacing = (float)PageSpacing;
DocumentProperty.Changed.Subscribe(_ => _renderer.UpdateDocument(Document));
PageSpacingProperty.Changed.Subscribe(f => _renderer.PageSpacing = (float)f.NewValue.Value);
}
public override void Render(DrawingContext context)
{
if (Document == null)
IsGeneratingDocument = _renderer.IsRendering;
if (_renderer.IsRendering)
return;
try
if (_renderer.RenderException != null)
{
IsGeneratingDocument = true;
Render(context, Document);
}
finally
{
IsGeneratingDocument = false;
context.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, Bounds.Width, Bounds.Height));
DrawException(context, _renderer.RenderException);
return;
}
Width = _renderer.Bounds.Width;
Height = _renderer.Bounds.Height;
if (_renderer.Picture == null)
return;
context.Custom(new SkPictureRenderOperation(_renderer.Picture, Bounds));
}
private void Render(DrawingContext context, IDocument document)
internal void InvalidateDocument()
{
//TODO optimize, currently we generate the document twice.
// First generation for calculating the sizes
// Second generation for the actual rendering.
var docSizeTracker = new SizeTrackingCanvas();
try
Dispatcher.UIThread.Post(() =>
{
DocumentGenerator.RenderDocument(docSizeTracker, document);
}
catch (Exception ex)
{
DrawException(context, ex);
return;
}
Width = docSizeTracker.PageSizes.Max(p => p.Width);
Height = docSizeTracker.PageSizes.Sum(p => p.Height) + ((docSizeTracker.PageSizes.Count - 1) * PageSpacing);
context.Custom(new DocumentRenderOperation(document, new Rect(0, 0, Width, Height), PageSpacing));
_renderer.UpdateDocument(Document);
InvalidateVisual();
});
}
private void DrawException(DrawingContext context, Exception ex)
+1 -1
View File
@@ -36,7 +36,7 @@ namespace QuestPDF.Previewer
public void InvalidatePreview()
{
_previewHost.InvalidateVisual();
_previewHost.InvalidateDocument();
}
}
}
@@ -0,0 +1,44 @@
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Skia;
using SkiaSharp;
namespace QuestPDF.Previewer
{
internal class SkPictureRenderOperation : ICustomDrawOperation
{
public SKPicture? Picture { get; }
public Rect Bounds { get; }
public SkPictureRenderOperation(SKPicture picture, Rect bounds)
{
Picture = picture;
Bounds = bounds;
}
public void Dispose() { }
public bool Equals(ICustomDrawOperation? other)
{
return false;
}
public bool HitTest(Point p)
{
return false;
}
public void Render(IDrawingContextImpl context)
{
if (Picture == null)
return;
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
if (canvas == null)
throw new InvalidOperationException($"Context needs to be ISkiaDrawingContextImpl but got {nameof(context)}");
canvas.DrawPicture(Picture);
}
}
}