Optimize document rendering.
We dont need to render the document two times anymore.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user