Implement document preview.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
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,50 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
internal sealed class PreviewerCanvas : SkiaCanvasBase, IRenderingCanvas
|
||||
{
|
||||
private bool _isFirstPage;
|
||||
private Size? _lastPageSize;
|
||||
private float? _distanceToCenter;
|
||||
|
||||
public float PageSpacing { get; set; }
|
||||
public float MaxPageWidth { get; set; }
|
||||
|
||||
public override void BeginDocument()
|
||||
{
|
||||
_isFirstPage = true;
|
||||
}
|
||||
|
||||
public override void BeginPage(Size size)
|
||||
{
|
||||
_lastPageSize = size;
|
||||
|
||||
if (!_isFirstPage)
|
||||
Canvas.Translate(0, PageSpacing);
|
||||
|
||||
if (MaxPageWidth > 0)
|
||||
{
|
||||
_distanceToCenter = (MaxPageWidth - size.Width) / 2;
|
||||
Canvas.Translate(_distanceToCenter.Value, 0);
|
||||
}
|
||||
|
||||
using var paint = new SKPaint() { Color = SKColors.White };
|
||||
Canvas.DrawRect(0, 0, size.Width, size.Height, paint);
|
||||
|
||||
_isFirstPage = false;
|
||||
}
|
||||
|
||||
public override void EndDocument() { }
|
||||
public override void EndPage()
|
||||
{
|
||||
if (_lastPageSize.HasValue)
|
||||
Canvas.Translate(0, _lastPageSize.Value.Height);
|
||||
|
||||
if (_distanceToCenter.HasValue)
|
||||
Canvas.Translate(-_distanceToCenter.Value, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
internal class PreviewerControl : Control
|
||||
{
|
||||
public static readonly StyledProperty<IDocument?> DocumentProperty =
|
||||
AvaloniaProperty.Register<PreviewerControl, IDocument?>(nameof(Document));
|
||||
|
||||
public IDocument? Document
|
||||
{
|
||||
get => GetValue(DocumentProperty);
|
||||
set => SetValue(DocumentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsGeneratingDocumentProperty =
|
||||
AvaloniaProperty.Register<PreviewerControl, bool>(nameof(IsGeneratingDocument));
|
||||
|
||||
public bool IsGeneratingDocument
|
||||
{
|
||||
get => GetValue(IsGeneratingDocumentProperty);
|
||||
set => SetValue(IsGeneratingDocumentProperty, value);
|
||||
}
|
||||
|
||||
public float PageSpacing { get; set; } = 20;
|
||||
|
||||
public PreviewerControl()
|
||||
{
|
||||
DocumentProperty
|
||||
.Changed
|
||||
.Subscribe(_ => InvalidateVisual());
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
if (Document == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
IsGeneratingDocument = true;
|
||||
Render(context, Document);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsGeneratingDocument = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Render(DrawingContext context, IDocument document)
|
||||
{
|
||||
//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
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
private void DrawException(DrawingContext context, Exception ex)
|
||||
{
|
||||
var exceptionMsg = string.Join("\n", ex.GetType(), ex.Message);
|
||||
var fmtText = new FormattedText($"Exception occured:\n{exceptionMsg}", Typeface.Default, 25, TextAlignment.Left, TextWrapping.Wrap, new Avalonia.Size(Parent.Bounds.Width / 2, Parent.Bounds.Height / 2));
|
||||
var center = new Point((Parent.Bounds.Width - fmtText.Bounds.Width) / 2, (Parent.Bounds.Height - fmtText.Bounds.Height) / 2);
|
||||
context.DrawText(Brushes.Black, center, fmtText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:previewer="clr-namespace:QuestPDF.Previewer"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="QuestPDF.Previewer.PreviewerView">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ScrollViewer Background="LightGray">
|
||||
<previewer:PreviewerControl x:Name="PreviewerSurface" Margin="25" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,42 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
internal class PreviewerView : UserControl
|
||||
{
|
||||
private readonly PreviewerControl _previewHost;
|
||||
|
||||
public static readonly StyledProperty<IDocument?> DocumentProperty =
|
||||
AvaloniaProperty.Register<PreviewerControl, IDocument?>(nameof(Document));
|
||||
|
||||
public IDocument? Document
|
||||
{
|
||||
get => GetValue(DocumentProperty);
|
||||
set => SetValue(DocumentProperty, value);
|
||||
}
|
||||
|
||||
public PreviewerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_previewHost = this.FindControl<PreviewerControl>("PreviewerSurface");
|
||||
|
||||
DocumentProperty
|
||||
.Changed
|
||||
.Subscribe(v => _previewHost.Document = v.NewValue.Value);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void InvalidatePreview()
|
||||
{
|
||||
_previewHost.InvalidateVisual();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user