diff --git a/QuestPDF.Previewer/DocumentRenderer.cs b/QuestPDF.Previewer/DocumentRenderer.cs index e3863e6..46864c7 100644 --- a/QuestPDF.Previewer/DocumentRenderer.cs +++ b/QuestPDF.Previewer/DocumentRenderer.cs @@ -1,22 +1,55 @@ -using QuestPDF.Drawing; +using System.Collections.ObjectModel; +using System.ComponentModel; +using Avalonia.Threading; +using QuestPDF.Drawing; +using QuestPDF.Fluent; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; using SkiaSharp; namespace QuestPDF.Previewer { - internal class DocumentRenderer + public record RenderedPageInfo(SKPicture Picture, Size Size); + + internal class DocumentRenderer : INotifyPropertyChanged { 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 event PropertyChangedEventHandler? PropertyChanged; + + private ObservableCollection _pages = new(); + public ObservableCollection Pages + { + get => _pages; + set + { + if (_pages != value) + { + _pages = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Pages))); + } + } + } + + private bool _isRendering; + public bool IsRendering + { + get => _isRendering; + private set + { + if (_isRendering != value) + { + _isRendering = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsRendering))); + } + } + } public void UpdateDocument(IDocument? document) { Document = document; - RenderException = null; if (document != null) { try @@ -26,9 +59,7 @@ namespace QuestPDF.Previewer } catch (Exception ex) { - RenderException = ex; - Picture?.Dispose(); - Picture = null; + RenderDocument(CreateExceptionDocument(ex)); } finally { @@ -39,23 +70,59 @@ namespace QuestPDF.Previewer private void RenderDocument(IDocument document) { - Picture?.Dispose(); + var canvas = new PreviewerCanvas(); - 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(); + + foreach (var pages in Pages) + pages?.Picture?.Dispose(); + Dispatcher.UIThread.Post(() => + { + Pages.Clear(); + Pages = new ObservableCollection(canvas.Pictures); + }); + } + + private static IDocument CreateExceptionDocument(Exception exception) + { + return Fluent.Document.Create(document => + { + document.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(1, Unit.Inch); + page.PageColor(Colors.Red.Lighten4); + page.DefaultTextStyle(x => x.FontSize(16)); + + page.Header() + .BorderBottom(2) + .BorderColor(Colors.Red.Medium) + .PaddingBottom(5) + .Text("Ooops! Something went wrong...").FontSize(28).FontColor(Colors.Red.Medium).Bold(); + + page.Content().PaddingVertical(20).Column(column => + { + var currentException = exception; + + while (currentException != null) + { + column.Item().Text(exception.GetType().Name).FontSize(20).SemiBold(); + column.Item().Text(exception.Message).FontSize(14); + column.Item().PaddingTop(10).Text(exception.StackTrace).FontSize(10).Light(); + + currentException = currentException.InnerException; + + if (currentException != null) + column.Item().PaddingVertical(15).LineHorizontal(2).LineColor(Colors.Red.Medium); + } + }); + }); + }); } } } diff --git a/QuestPDF.Previewer/HotReloadManager.cs b/QuestPDF.Previewer/HotReloadManager.cs index d6f015e..a8d34fe 100644 --- a/QuestPDF.Previewer/HotReloadManager.cs +++ b/QuestPDF.Previewer/HotReloadManager.cs @@ -7,24 +7,11 @@ namespace QuestPDF.Previewer /// internal static class HotReloadManager { - private static readonly List _actions = new(); - - public static void Register(Action action) - { - _actions.Add(action); - } - - public static void Unregister(Action action) - { - _actions.Remove(action); - } - - public static void ClearCache(Type[]? _) { } + public static event EventHandler? UpdateApplicationRequested; public static void UpdateApplication(Type[]? _) { - foreach (var action in _actions) - action(); + UpdateApplicationRequested?.Invoke(null, EventArgs.Empty); } } } diff --git a/QuestPDF.Previewer/PreviewerCanvas.cs b/QuestPDF.Previewer/PreviewerCanvas.cs index f168aef..a53ba0e 100644 --- a/QuestPDF.Previewer/PreviewerCanvas.cs +++ b/QuestPDF.Previewer/PreviewerCanvas.cs @@ -6,45 +6,38 @@ namespace QuestPDF.Previewer { internal sealed class PreviewerCanvas : SkiaCanvasBase, IRenderingCanvas { - private bool _isFirstPage; - private Size? _lastPageSize; - private float? _distanceToCenter; + private SKPictureRecorder? _currentRecorder; - public float PageSpacing { get; set; } - public float MaxPageWidth { get; set; } + private readonly List _pictures = new(); + public IReadOnlyList Pictures => _pictures; + + private Size? _currentSize; public override void BeginDocument() { - _isFirstPage = true; + _pictures.Clear(); } 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); - } + _currentSize = size; + _currentRecorder = new SKPictureRecorder(); + Canvas = _currentRecorder.BeginRecording(new SKRect(0, 0, size.Width, size.Height)); using var paint = new SKPaint() { Color = SKColors.White }; Canvas.DrawRect(0, 0, size.Width, size.Height, paint); + } - _isFirstPage = false; + public override void EndPage() + { + var picture = _currentRecorder?.EndRecording(); + if (picture != null && _currentSize.HasValue) + _pictures.Add(new RenderedPageInfo(picture, _currentSize.Value)); + + _currentRecorder?.Dispose(); + _currentRecorder = null; } 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); - } } } diff --git a/QuestPDF.Previewer/PreviewerControl.cs b/QuestPDF.Previewer/PreviewerControl.cs deleted file mode 100644 index 33d90d7..0000000 --- a/QuestPDF.Previewer/PreviewerControl.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Media; -using Avalonia.Threading; -using QuestPDF.Infrastructure; - -namespace QuestPDF.Previewer -{ - internal class PreviewerControl : Control - { - public static readonly StyledProperty DocumentProperty = - AvaloniaProperty.Register(nameof(Document)); - - public IDocument? Document - { - get => GetValue(DocumentProperty); - set => SetValue(DocumentProperty, value); - } - - public static readonly StyledProperty IsGeneratingDocumentProperty = - AvaloniaProperty.Register(nameof(IsGeneratingDocument)); - - public bool IsGeneratingDocument - { - get => GetValue(IsGeneratingDocumentProperty); - set => SetValue(IsGeneratingDocumentProperty, value); - } - - public static readonly StyledProperty PageSpacingProperty = - AvaloniaProperty.Register(nameof(PageSpacing), 20); - - public double PageSpacing - { - get => GetValue(PageSpacingProperty); - set => SetValue(PageSpacingProperty, value); - } - - private readonly DocumentRenderer _renderer = new(); - - public PreviewerControl() - { - ClipToBounds = true; - - _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) - { - IsGeneratingDocument = _renderer.IsRendering; - if (_renderer.IsRendering) - return; - - if (_renderer.RenderException != null) - { - 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 SkCustomDrawOperation( - new Rect(0, 0, Bounds.Width, Bounds.Height), - c => c.DrawPicture(_renderer.Picture))); - } - - internal void InvalidateDocument() - { - Dispatcher.UIThread.Post(() => - { - _renderer.UpdateDocument(Document); - InvalidateVisual(); - }); - } - - private void DrawException(DrawingContext context, Exception ex) - { - var parentBounds = Parent?.Bounds ?? Bounds; - - 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(parentBounds.Width / 2, parentBounds.Height / 2)); - - var center = new Point((parentBounds.Width - fmtText.Bounds.Width) / 2, (parentBounds.Height - fmtText.Bounds.Height) / 2); - context.DrawText(Brushes.Black, center, fmtText); - } - } -} diff --git a/QuestPDF.Previewer/PreviewerPageControl.cs b/QuestPDF.Previewer/PreviewerPageControl.cs new file mode 100644 index 0000000..8025182 --- /dev/null +++ b/QuestPDF.Previewer/PreviewerPageControl.cs @@ -0,0 +1,44 @@ +using System.Reactive.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; + +namespace QuestPDF.Previewer +{ + internal class PreviewerPageControl : Control + { + public static readonly StyledProperty PageProperty = + AvaloniaProperty.Register(nameof(Page)); + + public RenderedPageInfo? Page + { + get => GetValue(PageProperty); + set => SetValue(PageProperty, value); + } + + public PreviewerPageControl() + { + PageProperty.Changed.Take(1).Subscribe(p => + { + var size = p.NewValue.Value?.Size ?? Infrastructure.Size.Zero; + Width = size.Width; + Height = size.Height; + }); + + ClipToBounds = true; + } + + public override void Render(DrawingContext context) + { + base.Render(context); + + //context.DrawRectangle(Brushes.Red, null, new Rect(0, 0, Bounds.Width, Bounds.Height)); + + var picture = Page?.Picture; + if (picture != null) + context.Custom(new SkCustomDrawOperation( + new Rect(0, 0, Bounds.Width, Bounds.Height), + c => c.DrawPicture(picture))); + } + } +} diff --git a/QuestPDF.Previewer/PreviewerWindow.axaml b/QuestPDF.Previewer/PreviewerWindow.axaml index 8ddf8c7..394537e 100644 --- a/QuestPDF.Previewer/PreviewerWindow.axaml +++ b/QuestPDF.Previewer/PreviewerWindow.axaml @@ -9,6 +9,7 @@ Icon="/Images/Logo.png" UseLayoutRounding="True" Background="{x:Null}" + x:Name="Window" Title="QuestPDF Document Preview">