|
|
@@ -0,0 +1,188 @@
|
|
|
+using Avalonia;
|
|
|
+using Avalonia.Controls;
|
|
|
+using Avalonia.Input;
|
|
|
+using Avalonia.Markup.Xaml;
|
|
|
+using Avalonia.Media;
|
|
|
+using DynamicData.Binding;
|
|
|
+using InABox.Avalonia;
|
|
|
+using InABox.Avalonia.Components;
|
|
|
+using InABox.Avalonia.Converters;
|
|
|
+using InABox.Avalonia.Platform;
|
|
|
+using InABox.Core;
|
|
|
+using System;
|
|
|
+using System.IO;
|
|
|
+using System.Reactive;
|
|
|
+using System.Threading.Tasks;
|
|
|
+
|
|
|
+namespace PRS.Avalonia.Components;
|
|
|
+
|
|
|
+public partial class DocumentViewer : UserControl
|
|
|
+{
|
|
|
+ public static StyledProperty<string?> FileNameProperty =
|
|
|
+ AvaloniaProperty.Register<DocumentViewer, string?>(nameof(FileName));
|
|
|
+ public string? FileName
|
|
|
+ {
|
|
|
+ get => GetValue(FileNameProperty);
|
|
|
+ set => SetValue(FileNameProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static StyledProperty<byte[]?> DataProperty =
|
|
|
+ AvaloniaProperty.Register<DocumentViewer, byte[]?>(nameof(Data));
|
|
|
+ public byte[]? Data
|
|
|
+ {
|
|
|
+ get => GetValue(DataProperty);
|
|
|
+ set => SetValue(DataProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private double ScaleFactor = 1.0;
|
|
|
+ private double _originalScaleFactor = 1.0;
|
|
|
+ private double _startScaleFactor = 1.0;
|
|
|
+
|
|
|
+ // Center of the image.
|
|
|
+ private Point ImageCenter = new();
|
|
|
+ private IImage? _image;
|
|
|
+ private double ImageWidth => _image?.Size.Width ?? 0;
|
|
|
+ private double ImageHeight => _image?.Size.Height ?? 0;
|
|
|
+
|
|
|
+ public DocumentViewer()
|
|
|
+ {
|
|
|
+ InitializeComponent();
|
|
|
+
|
|
|
+ this.GetPropertyChangedObservable(FileNameProperty).Subscribe(x => Update().ContinueWith(ContinueTask));
|
|
|
+ this.GetPropertyChangedObservable(DataProperty).Subscribe(x => Update().ContinueWith(ContinueTask));
|
|
|
+
|
|
|
+ OuterCanvas.AddHandler(PanAndZoomGestureRecognizer.PanAndZoomEndedEvent, ImageContainer_PinchEnded);
|
|
|
+ OuterCanvas.AddHandler(PanAndZoomGestureRecognizer.PanAndZoomEvent, ImageContainer_Pinch);
|
|
|
+
|
|
|
+ OuterCanvas.SizeChanged += ImageContainer_SizeChanged;
|
|
|
+ OuterCanvas.PointerWheelChanged += OuterCanvas_PointerWheelChanged;
|
|
|
+ }
|
|
|
+
|
|
|
+ private const double _wheelSpeed = 0.1;
|
|
|
+ private const double _panSpeed = 30;
|
|
|
+
|
|
|
+ private void Pan(double x, double y)
|
|
|
+ {
|
|
|
+ ImageCenter += new Vector(x, y);
|
|
|
+ UpdateCanvasPosition();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OuterCanvas_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
|
|
|
+ {
|
|
|
+ if (e.KeyModifiers.HasFlag(KeyModifiers.Control))
|
|
|
+ {
|
|
|
+ var pos = e.GetPosition(OuterCanvas);
|
|
|
+ var wheelSpeed = _wheelSpeed;
|
|
|
+ Zoom(pos, pos, e.Delta.Y > 0 ? ScaleFactor * (1 + e.Delta.Y * wheelSpeed) : ScaleFactor / (1 + (-e.Delta.Y) * wheelSpeed));
|
|
|
+ }
|
|
|
+ else if(e.KeyModifiers.HasFlag(KeyModifiers.Shift))
|
|
|
+ {
|
|
|
+ Pan(e.Delta.Y * _panSpeed, e.Delta.X * _panSpeed);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Pan(e.Delta.X * _panSpeed, e.Delta.Y * _panSpeed);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ImageContainer_SizeChanged(object? sender, SizeChangedEventArgs e)
|
|
|
+ {
|
|
|
+ PositionImage();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ImageContainer_Pinch(object? sender, PanAndZoomEventArgs e)
|
|
|
+ {
|
|
|
+ Zoom(e.ScaleOrigin - e.Pan, e.ScaleOrigin, _originalScaleFactor * e.Scale);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ImageContainer_PinchEnded(object? sender, PanAndZoomEndedEventArgs e)
|
|
|
+ {
|
|
|
+ _originalScaleFactor = ScaleFactor;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Zoom(Point originalOrigin, Point newOrigin, double newScaleFactor)
|
|
|
+ {
|
|
|
+ // Convert Scale Origin to image coordinates (relative to center).
|
|
|
+ // Work out where this position will move to under the new scaling.
|
|
|
+ // Adjust so that these are the same.
|
|
|
+
|
|
|
+ var pos = originalOrigin - ImageCenter;
|
|
|
+ var contentMPos = pos / ScaleFactor;
|
|
|
+
|
|
|
+ ScaleFactor = Math.Max(_startScaleFactor, newScaleFactor);
|
|
|
+
|
|
|
+ var scaledPos = ImageCenter + contentMPos * ScaleFactor;
|
|
|
+ var offset = scaledPos - newOrigin;
|
|
|
+
|
|
|
+ ImageCenter -= offset;
|
|
|
+
|
|
|
+ UpdateCanvasPosition();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void PositionImage()
|
|
|
+ {
|
|
|
+ var canvasWidth = OuterCanvas.Bounds.Width;
|
|
|
+ var canvasHeight = OuterCanvas.Bounds.Height;
|
|
|
+
|
|
|
+ var scaleFactor = Math.Min(canvasWidth / ImageWidth, canvasHeight / ImageHeight);
|
|
|
+ ScaleFactor = scaleFactor;
|
|
|
+ _originalScaleFactor = ScaleFactor;
|
|
|
+ _startScaleFactor = ScaleFactor;
|
|
|
+
|
|
|
+ ImageCenter = new Point(
|
|
|
+ OuterCanvas.Bounds.Width / 2,
|
|
|
+ OuterCanvas.Bounds.Height / 2);
|
|
|
+
|
|
|
+ UpdateCanvasPosition();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void UpdateCanvasPosition()
|
|
|
+ {
|
|
|
+ var imageWidth = ImageWidth * ScaleFactor;
|
|
|
+ var imageHeight = ImageHeight * ScaleFactor;
|
|
|
+
|
|
|
+ Image.Width = imageWidth;
|
|
|
+ Image.Height = imageHeight;
|
|
|
+
|
|
|
+ Canvas.SetLeft(Image, ImageCenter.X - imageWidth / 2);
|
|
|
+ Canvas.SetTop(Image, ImageCenter.Y - imageHeight / 2);
|
|
|
+
|
|
|
+ // OuterCanvas.Width = ImageWidth;
|
|
|
+ // OuterCanvas.Height = ImageHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ContinueTask(Task task)
|
|
|
+ {
|
|
|
+ if(task.Exception is not null)
|
|
|
+ {
|
|
|
+ MobileLogging.Log(task.Exception);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task Update()
|
|
|
+ {
|
|
|
+ if (Data is not null && FileName is not null)
|
|
|
+ {
|
|
|
+ byte[]? bitmap;
|
|
|
+ if(Path.GetExtension(FileName).ToUpper() == ".PDF")
|
|
|
+ {
|
|
|
+ bitmap = await PlatformTools.PdfRenderer.PdfToImageAsync(Data, 0, 300);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ bitmap = Data;
|
|
|
+ }
|
|
|
+ if(bitmap is not null)
|
|
|
+ {
|
|
|
+ _image = ByteArrayToImageSourceConverter.Instance.Convert(bitmap);
|
|
|
+ Image.Source = _image;
|
|
|
+ Image.IsVisible = true;
|
|
|
+ NoImage.IsVisible = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ NoImage.IsVisible = true;
|
|
|
+ Image.IsVisible = false;
|
|
|
+ }
|
|
|
+}
|