Code Cleanup

This commit is contained in:
ema
2024-12-11 22:17:26 +08:00
parent c056438c58
commit 28ec7655f8
89 changed files with 7796 additions and 7698 deletions

View File

@@ -1,17 +1,17 @@
// Copyright © 2018 Paddy Xu
//
//
// This file is part of QuickLook program.
//
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -21,42 +21,41 @@ using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Media.Imaging;
namespace QuickLook.Plugin.PDFViewer
namespace QuickLook.Plugin.PDFViewer;
internal class AsyncPageToThumbnailConverter : IMultiValueConverter
{
internal class AsyncPageToThumbnailConverter : IMultiValueConverter
private static readonly BitmapImage Loading =
new BitmapImage(
new Uri("pack://application:,,,/QuickLook.Plugin.PdfViewer;component/Resources/loading.png"));
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
private static readonly BitmapImage Loading =
new BitmapImage(
new Uri("pack://application:,,,/QuickLook.Plugin.PdfViewer;component/Resources/loading.png"));
if (values.Length < 2)
throw new Exception("PageIdToImageConverter");
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
if (values[0] is not PdfDocumentWrapper handle) return null;
var pageId = (int)values[1];
if (pageId < 0) return null;
var task = Task.Run(() =>
{
if (values.Length < 2)
throw new Exception("PageIdToImageConverter");
if (!(values[0] is PdfDocumentWrapper handle)) return null;
var pageId = (int) values[1];
if (pageId < 0) return null;
var task = Task.Run(() =>
try
{
try
{
return handle.RenderThumbnail(pageId);
}
catch (Exception)
{
return Loading;
}
});
return handle.RenderThumbnail(pageId);
}
catch (Exception)
{
return Loading;
}
});
return new NotifyTaskCompletion<BitmapSource>(task, Loading);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
return new NotifyTaskCompletion<BitmapSource>(task, Loading);
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@@ -1,48 +1,48 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="ListBoxItemStyleNoFocusedBorder" TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
<Border x:Name="Bd"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
ContentTemplate="{TemplateBinding ContentTemplate}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="Transparent" />
<Setter Property="BorderBrush" TargetName="Bd" Value="Transparent" />
<Setter TargetName="Bd" Property="Background" Value="Transparent" />
<Setter TargetName="Bd" Property="BorderBrush" Value="Transparent" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="Transparent" />
<Setter Property="BorderBrush" TargetName="Bd" Value="Transparent" />
<Setter TargetName="Bd" Property="Background" Value="Transparent" />
<Setter TargetName="Bd" Property="BorderBrush" Value="Transparent" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="Transparent" />
<Setter Property="BorderBrush" TargetName="Bd" Value="Transparent" />
<Setter TargetName="Bd" Property="Background" Value="Transparent" />
<Setter TargetName="Bd" Property="BorderBrush" Value="Transparent" />
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="TextElement.Foreground" TargetName="Bd"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
<Setter TargetName="Bd" Property="TextElement.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

View File

@@ -1,17 +1,17 @@
// Copyright © 2018 Paddy Xu
//
//
// This file is part of QuickLook program.
//
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -19,71 +19,73 @@ using System;
using System.ComponentModel;
using System.Threading.Tasks;
namespace QuickLook.Plugin.PDFViewer
namespace QuickLook.Plugin.PDFViewer;
public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged
{
public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged
private readonly TResult _loading;
public NotifyTaskCompletion(Task<TResult> task, TResult loading = default(TResult))
{
private readonly TResult _loading;
public NotifyTaskCompletion(Task<TResult> task, TResult loading = default(TResult))
Task = task;
_loading = loading;
if (!task.IsCompleted)
{
Task = task;
_loading = loading;
if (!task.IsCompleted)
{
var _ = WatchTaskAsync(task);
}
}
public Task<TResult> Task { get; }
public TResult Result => Task.Status == TaskStatus.RanToCompletion ? Task.Result : _loading;
public TaskStatus Status => Task.Status;
public bool IsCompleted => Task.IsCompleted;
public bool IsNotCompleted => !Task.IsCompleted;
public bool IsSuccessfullyCompleted => Task.Status ==
TaskStatus.RanToCompletion;
public bool IsCanceled => Task.IsCanceled;
public bool IsFaulted => Task.IsFaulted;
public AggregateException Exception => Task.Exception;
public Exception InnerException => Exception?.InnerException;
public string ErrorMessage => InnerException?.Message;
public event PropertyChangedEventHandler PropertyChanged;
private async Task WatchTaskAsync(Task task)
{
try
{
await task;
}
catch
{
// ignored
}
var propertyChanged = PropertyChanged;
if (propertyChanged == null)
return;
propertyChanged(this, new PropertyChangedEventArgs("Status"));
propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted"));
if (task.IsCanceled)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
}
else if (task.IsFaulted)
{
propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
propertyChanged(this, new PropertyChangedEventArgs("Exception"));
propertyChanged(this,
new PropertyChangedEventArgs("InnerException"));
propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
}
else
{
propertyChanged(this,
new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("Result"));
}
var _ = WatchTaskAsync(task);
}
}
}
public Task<TResult> Task { get; }
public TResult Result => Task.Status == TaskStatus.RanToCompletion ? Task.Result : _loading;
public TaskStatus Status => Task.Status;
public bool IsCompleted => Task.IsCompleted;
public bool IsNotCompleted => !Task.IsCompleted;
public bool IsSuccessfullyCompleted => Task.Status ==
TaskStatus.RanToCompletion;
public bool IsCanceled => Task.IsCanceled;
public bool IsFaulted => Task.IsFaulted;
public AggregateException Exception => Task.Exception;
public Exception InnerException => Exception?.InnerException;
public string ErrorMessage => InnerException?.Message;
public event PropertyChangedEventHandler PropertyChanged;
private async Task WatchTaskAsync(Task task)
{
try
{
await task;
}
catch
{
// ignored
}
var propertyChanged = PropertyChanged;
if (propertyChanged == null)
return;
propertyChanged(this, new PropertyChangedEventArgs("Status"));
propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted"));
if (task.IsCanceled)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
}
else if (task.IsFaulted)
{
propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
propertyChanged(this, new PropertyChangedEventArgs("Exception"));
propertyChanged(this,
new PropertyChangedEventArgs("InnerException"));
propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
}
else
{
propertyChanged(this,
new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("Result"));
}
}
}

View File

@@ -1,17 +1,17 @@
// Copyright © 2018 Paddy Xu
//
//
// This file is part of QuickLook program.
//
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -19,40 +19,39 @@ using System;
using System.IO;
using PdfiumViewer;
namespace QuickLook.Plugin.PDFViewer
namespace QuickLook.Plugin.PDFViewer;
public class PdfDocumentWrapper : IDisposable
{
public class PdfDocumentWrapper : IDisposable
public PdfDocumentWrapper(Stream stream)
{
public PdfDocumentWrapper(Stream stream)
{
PdfStream = new MemoryStream((int) stream.Length);
stream.CopyTo(PdfStream);
PdfStream = new MemoryStream((int)stream.Length);
stream.CopyTo(PdfStream);
PdfDocument = PdfDocument.Load(PdfStream);
}
public PdfDocument PdfDocument { get; private set; }
public MemoryStream PdfStream { get; private set; }
public void Dispose()
{
PdfDocument.Dispose();
PdfDocument = null;
PdfStream.Dispose();
PdfStream = null;
}
public void Refresh()
{
var oldD = PdfDocument;
PdfStream.Position = 0;
var newObj = new PdfDocumentWrapper(PdfStream);
PdfDocument = newObj.PdfDocument;
PdfStream = newObj.PdfStream;
oldD.Dispose();
}
PdfDocument = PdfDocument.Load(PdfStream);
}
}
public PdfDocument PdfDocument { get; private set; }
public MemoryStream PdfStream { get; private set; }
public void Dispose()
{
PdfDocument.Dispose();
PdfDocument = null;
PdfStream.Dispose();
PdfStream = null;
}
public void Refresh()
{
var oldD = PdfDocument;
PdfStream.Position = 0;
var newObj = new PdfDocumentWrapper(PdfStream);
PdfDocument = newObj.PdfDocument;
PdfStream = newObj.PdfStream;
oldD.Dispose();
}
}

View File

@@ -1,76 +1,75 @@
// Copyright © 2018 Paddy Xu
//
//
// This file is part of QuickLook program.
//
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Drawing;
using System.Windows.Media.Imaging;
using PdfiumViewer;
using QuickLook.Common.ExtensionMethods;
using QuickLook.Common.Helpers;
using System;
using System.Drawing;
using System.Windows.Media.Imaging;
namespace QuickLook.Plugin.PDFViewer
namespace QuickLook.Plugin.PDFViewer;
internal static class PdfPageExtension
{
internal static class PdfPageExtension
private static int _renderCount;
private static readonly object LockObj = new object();
public static BitmapSource RenderThumbnail(this PdfDocumentWrapper doc, int page)
{
private static int _renderCount;
private static readonly object LockObj = new object();
public static BitmapSource RenderThumbnail(this PdfDocumentWrapper doc, int page)
lock (LockObj)
{
lock (LockObj)
if (_renderCount++ == 50)
{
if (_renderCount++ == 50)
{
doc.Refresh();
_renderCount = 0;
}
doc.Refresh();
_renderCount = 0;
}
var size = doc.PdfDocument.PageSizes[page];
var factorX = 60d / size.Width;
var factorY = 120d / size.Height;
return doc.Render(page, Math.Min(factorX, factorY), false);
}
public static BitmapSource Render(this PdfDocumentWrapper doc, int page, double factor, bool fixDpi = true)
{
var scale = DisplayDeviceHelper.GetCurrentScaleFactor();
var dpiX = fixDpi ? scale.Horizontal * DisplayDeviceHelper.DefaultDpi : 96;
var dpiY = fixDpi ? scale.Vertical * DisplayDeviceHelper.DefaultDpi : 96;
var size = doc.PdfDocument.PageSizes[page];
var factorX = 60d / size.Width;
var factorY = 120d / size.Height;
Bitmap bitmap;
lock (LockObj)
{
var size = doc.PdfDocument.PageSizes[page];
var realWidth = (int) Math.Round(size.Width * scale.Horizontal * factor);
var realHeight = (int) Math.Round(size.Height * scale.Vertical * factor);
bitmap = doc.PdfDocument.Render(page, realWidth, realHeight, dpiX, dpiY, PdfRotation.Rotate0,
PdfRenderFlags.LimitImageCacheSize | PdfRenderFlags.LcdText | PdfRenderFlags.Annotations |
PdfRenderFlags.ForPrinting, true) as Bitmap;
}
var bs = bitmap?.ToBitmapSource();
bitmap?.Dispose();
bs?.Freeze();
return bs;
}
return doc.Render(page, Math.Min(factorX, factorY), false);
}
}
public static BitmapSource Render(this PdfDocumentWrapper doc, int page, double factor, bool fixDpi = true)
{
var scale = DisplayDeviceHelper.GetCurrentScaleFactor();
var dpiX = fixDpi ? scale.Horizontal * DisplayDeviceHelper.DefaultDpi : 96;
var dpiY = fixDpi ? scale.Vertical * DisplayDeviceHelper.DefaultDpi : 96;
Bitmap bitmap;
lock (LockObj)
{
var size = doc.PdfDocument.PageSizes[page];
var realWidth = (int)Math.Round(size.Width * scale.Horizontal * factor);
var realHeight = (int)Math.Round(size.Height * scale.Vertical * factor);
bitmap = doc.PdfDocument.Render(page, realWidth, realHeight, dpiX, dpiY, PdfRotation.Rotate0,
PdfRenderFlags.LimitImageCacheSize | PdfRenderFlags.LcdText | PdfRenderFlags.Annotations |
PdfRenderFlags.ForPrinting, true) as Bitmap;
}
var bs = bitmap?.ToBitmapSource();
bitmap?.Dispose();
bs?.Freeze();
return bs;
}
}

View File

@@ -1,15 +1,15 @@
<UserControl x:Class="QuickLook.Plugin.PDFViewer.PdfViewerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:QuickLook.Plugin.PDFViewer"
xmlns:imageViewer="clr-namespace:QuickLook.Plugin.ImageViewer;assembly=QuickLook.Plugin.ImageViewer"
mc:Ignorable="d"
xmlns:local="clr-namespace:QuickLook.Plugin.PDFViewer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="thisPdfViewer"
UseLayoutRounding="True"
d:DesignHeight="476.974"
d:DesignWidth="720.29">
d:DesignWidth="720.29"
UseLayoutRounding="True"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<local:AsyncPageToThumbnailConverter x:Key="AsyncPageToThumbnailConverter" />
@@ -24,16 +24,19 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="listThumbnails" Grid.Column="0" Width="150"
SelectedIndex="0"
Focusable="False"
<ListBox x:Name="listThumbnails"
Grid.Column="0"
Width="150"
HorizontalContentAlignment="Center"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
Background="Transparent" BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Background="Transparent"
BorderThickness="0"
Focusable="False"
ItemContainerStyle="{Binding Mode=OneWay, Source={StaticResource ListBoxItemStyleNoFocusedBorder}}"
ItemsSource="{Binding ElementName=thisPdfViewer, Path=PageThumbnails}"
ItemContainerStyle="{Binding Mode=OneWay, Source={StaticResource ListBoxItemStyleNoFocusedBorder}}">
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedIndex="0"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="150"
@@ -49,11 +52,15 @@
<RowDefinition Height="*" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Border x:Name="bbd" Grid.Row="1" Grid.Column="1" BorderThickness="1" BorderBrush="Gray">
<Border x:Name="bbd"
Grid.Row="1"
Grid.Column="1"
BorderBrush="Gray"
BorderThickness="1">
<Image Source="{Binding Path=Result}">
<Image.DataContext>
<MultiBinding Converter="{StaticResource AsyncPageToThumbnailConverter}">
<Binding Path="PdfDocumentWrapper" ElementName="thisPdfViewer" />
<Binding ElementName="thisPdfViewer" Path="PdfDocumentWrapper" />
<Binding />
</MultiBinding>
</Image.DataContext>
@@ -64,9 +71,7 @@
-->
</Grid>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"
Value="True">
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Value="True">
<Setter TargetName="bbd" Property="BorderBrush" Value="#DD0027FF" />
</DataTrigger>
</DataTemplate.Triggers>
@@ -74,8 +79,12 @@
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1" Background="#33FFFFFF">
<imageViewer:ImagePanel x:Name="pagePanel" RenderMode="NearestNeighbor" ShowZoomLevelInfo="False"
ZoomWithControlKey="True" BackgroundVisibility="Collapsed" MetaIconVisibility="Collapsed" />
<imageViewer:ImagePanel x:Name="pagePanel"
BackgroundVisibility="Collapsed"
MetaIconVisibility="Collapsed"
RenderMode="NearestNeighbor"
ShowZoomLevelInfo="False"
ZoomWithControlKey="True" />
</Grid>
</Grid>
</UserControl>

View File

@@ -1,20 +1,22 @@
// Copyright © 2018 Paddy Xu
//
//
// This file is part of QuickLook program.
//
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using PdfiumViewer;
using QuickLook.Common.ExtensionMethods;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
@@ -24,250 +26,247 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using PdfiumViewer;
using QuickLook.Common.ExtensionMethods;
namespace QuickLook.Plugin.PDFViewer
namespace QuickLook.Plugin.PDFViewer;
/// <summary>
/// Interaction logic for PdfViewer.xaml
/// </summary>
public partial class PdfViewerControl : UserControl, INotifyPropertyChanged, IDisposable
{
/// <summary>
/// Interaction logic for PdfViewer.xaml
/// </summary>
public partial class PdfViewerControl : UserControl, INotifyPropertyChanged, IDisposable
private int _changePageDeltaSum;
private bool _initPage = true;
private double _maxZoomFactor = double.NaN;
private double _minZoomFactor = double.NaN;
private bool _pdfLoaded;
private double _viewRenderFactor = double.NaN;
public PdfViewerControl()
{
private int _changePageDeltaSum;
private bool _initPage = true;
private double _maxZoomFactor = double.NaN;
private double _minZoomFactor = double.NaN;
InitializeComponent();
private bool _pdfLoaded;
private double _viewRenderFactor = double.NaN;
// remove theme used in designer
Resources.MergedDictionaries.RemoveAt(0);
public PdfViewerControl()
listThumbnails.SelectionChanged += UpdatePageViewWhenSelectionChanged;
pagePanel.ZoomChanged += ReRenderCurrentPageDelayed;
pagePanel.ImageScrolled += NavigatePage;
}
public ObservableCollection<int> PageThumbnails { get; set; } = new ObservableCollection<int>();
public int TotalPages => PdfDocumentWrapper.PdfDocument.PageCount;
public int CurrentPage
{
get => listThumbnails.SelectedIndex;
set
{
InitializeComponent();
// remove theme used in designer
Resources.MergedDictionaries.RemoveAt(0);
listThumbnails.SelectionChanged += UpdatePageViewWhenSelectionChanged;
pagePanel.ZoomChanged += ReRenderCurrentPageDelayed;
pagePanel.ImageScrolled += NavigatePage;
}
public ObservableCollection<int> PageThumbnails { get; set; } = new ObservableCollection<int>();
public int TotalPages => PdfDocumentWrapper.PdfDocument.PageCount;
public int CurrentPage
{
get => listThumbnails.SelectedIndex;
set
{
listThumbnails.SelectedIndex = value;
listThumbnails.ScrollIntoView(listThumbnails.SelectedItem);
CurrentPageChanged?.Invoke(this, new EventArgs());
OnPropertyChanged();
}
}
public PdfDocumentWrapper PdfDocumentWrapper { get; private set; }
public void Dispose()
{
GC.SuppressFinalize(this);
if (pagePanel != null)
{
pagePanel.ZoomChanged -= ReRenderCurrentPageDelayed;
pagePanel.ImageScrolled -= NavigatePage;
}
_pdfLoaded = false;
PdfDocumentWrapper?.Dispose();
PdfDocumentWrapper = null;
}
public event PropertyChangedEventHandler PropertyChanged;
private void ReRenderCurrentPageDelayed(object sender, EventArgs e)
{
ReRenderCurrentPage();
}
public event EventHandler CurrentPageChanged;
private void NavigatePage(object sender, int delta)
{
if (!_pdfLoaded)
return;
var pos = pagePanel.GetScrollPosition();
var size = pagePanel.GetScrollSize();
const double tolerance = 0.0001d;
if (Math.Abs(pos.Y) < tolerance && delta > 0)
{
_changePageDeltaSum += delta;
if (Math.Abs(_changePageDeltaSum) < 20)
return;
PrevPage();
_changePageDeltaSum = 0;
}
else if (Math.Abs(pos.Y - size.Height) < tolerance && delta < -0)
{
_changePageDeltaSum += delta;
if (Math.Abs(_changePageDeltaSum) < 20)
return;
NextPage();
_changePageDeltaSum = 0;
}
}
private void NextPage()
{
if (CurrentPage < TotalPages - 1)
{
CurrentPage++;
pagePanel.ScrollToTop();
}
}
private void PrevPage()
{
if (CurrentPage > 0)
{
CurrentPage--;
pagePanel.ScrollToBottom();
}
}
private void ReRenderCurrentPage()
{
if (!_pdfLoaded)
return;
if (CurrentPage < 0 || CurrentPage >= TotalPages)
return;
Debug.WriteLine($"Re-rendering page {CurrentPage}");
var pos = pagePanel.GetScrollPosition();
double factor;
// First time showing. Set thresholds here.
if (double.IsNaN(_minZoomFactor) || double.IsNaN(_maxZoomFactor))
{
factor = Math.Min(pagePanel.ActualHeight / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Height,
pagePanel.ActualWidth / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Width);
_viewRenderFactor = factor;
_minZoomFactor = 0.1 * factor;
_maxZoomFactor = 5 * factor;
}
else if (pagePanel.ZoomToFit)
{
factor = Math.Min(pagePanel.ActualHeight / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Height,
pagePanel.ActualWidth / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Width);
}
else
{
factor = pagePanel.ZoomFactor * _viewRenderFactor;
factor = Math.Max(factor, _minZoomFactor);
factor = Math.Min(factor, _maxZoomFactor);
pagePanel.MinZoomFactor = _minZoomFactor / factor;
pagePanel.MaxZoomFactor = _maxZoomFactor / factor;
}
var image = PdfDocumentWrapper.Render(CurrentPage, factor);
pagePanel.Source = image;
pagePanel.ResetZoom();
_viewRenderFactor = factor;
pagePanel.SetScrollPosition(pos);
Dispatcher.Delay(500, t => GC.Collect());
}
private void UpdatePageViewWhenSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_pdfLoaded)
return;
if (CurrentPage == -1)
return;
listThumbnails.SelectedIndex = value;
listThumbnails.ScrollIntoView(listThumbnails.SelectedItem);
CurrentPageChanged?.Invoke(this, new EventArgs());
ReRenderCurrentPage();
if (_initPage)
{
_initPage = false;
pagePanel.DoZoomToFit();
}
}
public static Size GetDesiredControlSizeByFirstPage(string path)
{
Size size;
using (var s = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var tempHandle = PdfDocument.Load(s))
{
size = new Size(0, 0);
tempHandle.PageSizes.Take(5).ForEach(p =>
{
size.Width = Math.Max(size.Width, p.Width);
size.Height = Math.Max(size.Height, p.Height);
});
if (tempHandle.PageCount > 1)
size.Width += /*listThumbnails.ActualWidth*/ 150;
}
}
return new Size(size.Width * 3, size.Height * 3);
}
public void LoadPdf(string path)
{
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
PdfDocumentWrapper = new PdfDocumentWrapper(stream);
_pdfLoaded = true;
if (PdfDocumentWrapper.PdfDocument.PageCount < 2)
listThumbnails.Visibility = Visibility.Collapsed;
BeginLoadThumbnails();
}
public void LoadPdf(MemoryStream stream)
{
stream.Position = 0;
PdfDocumentWrapper = new PdfDocumentWrapper(stream);
_pdfLoaded = true;
if (PdfDocumentWrapper.PdfDocument.PageCount < 2)
listThumbnails.Visibility = Visibility.Collapsed;
BeginLoadThumbnails();
}
private void BeginLoadThumbnails()
{
Enumerable.Range(0, PdfDocumentWrapper.PdfDocument.PageCount).ForEach(PageThumbnails.Add);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
OnPropertyChanged();
}
}
}
public PdfDocumentWrapper PdfDocumentWrapper { get; private set; }
public void Dispose()
{
GC.SuppressFinalize(this);
if (pagePanel != null)
{
pagePanel.ZoomChanged -= ReRenderCurrentPageDelayed;
pagePanel.ImageScrolled -= NavigatePage;
}
_pdfLoaded = false;
PdfDocumentWrapper?.Dispose();
PdfDocumentWrapper = null;
}
public event PropertyChangedEventHandler PropertyChanged;
private void ReRenderCurrentPageDelayed(object sender, EventArgs e)
{
ReRenderCurrentPage();
}
public event EventHandler CurrentPageChanged;
private void NavigatePage(object sender, int delta)
{
if (!_pdfLoaded)
return;
var pos = pagePanel.GetScrollPosition();
var size = pagePanel.GetScrollSize();
const double tolerance = 0.0001d;
if (Math.Abs(pos.Y) < tolerance && delta > 0)
{
_changePageDeltaSum += delta;
if (Math.Abs(_changePageDeltaSum) < 20)
return;
PrevPage();
_changePageDeltaSum = 0;
}
else if (Math.Abs(pos.Y - size.Height) < tolerance && delta < -0)
{
_changePageDeltaSum += delta;
if (Math.Abs(_changePageDeltaSum) < 20)
return;
NextPage();
_changePageDeltaSum = 0;
}
}
private void NextPage()
{
if (CurrentPage < TotalPages - 1)
{
CurrentPage++;
pagePanel.ScrollToTop();
}
}
private void PrevPage()
{
if (CurrentPage > 0)
{
CurrentPage--;
pagePanel.ScrollToBottom();
}
}
private void ReRenderCurrentPage()
{
if (!_pdfLoaded)
return;
if (CurrentPage < 0 || CurrentPage >= TotalPages)
return;
Debug.WriteLine($"Re-rendering page {CurrentPage}");
var pos = pagePanel.GetScrollPosition();
double factor;
// First time showing. Set thresholds here.
if (double.IsNaN(_minZoomFactor) || double.IsNaN(_maxZoomFactor))
{
factor = Math.Min(pagePanel.ActualHeight / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Height,
pagePanel.ActualWidth / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Width);
_viewRenderFactor = factor;
_minZoomFactor = 0.1 * factor;
_maxZoomFactor = 5 * factor;
}
else if (pagePanel.ZoomToFit)
{
factor = Math.Min(pagePanel.ActualHeight / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Height,
pagePanel.ActualWidth / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Width);
}
else
{
factor = pagePanel.ZoomFactor * _viewRenderFactor;
factor = Math.Max(factor, _minZoomFactor);
factor = Math.Min(factor, _maxZoomFactor);
pagePanel.MinZoomFactor = _minZoomFactor / factor;
pagePanel.MaxZoomFactor = _maxZoomFactor / factor;
}
var image = PdfDocumentWrapper.Render(CurrentPage, factor);
pagePanel.Source = image;
pagePanel.ResetZoom();
_viewRenderFactor = factor;
pagePanel.SetScrollPosition(pos);
Dispatcher.Delay(500, t => GC.Collect());
}
private void UpdatePageViewWhenSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_pdfLoaded)
return;
if (CurrentPage == -1)
return;
CurrentPageChanged?.Invoke(this, new EventArgs());
ReRenderCurrentPage();
if (_initPage)
{
_initPage = false;
pagePanel.DoZoomToFit();
}
}
public static Size GetDesiredControlSizeByFirstPage(string path)
{
Size size;
using (var s = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var tempHandle = PdfDocument.Load(s))
{
size = new Size(0, 0);
tempHandle.PageSizes.Take(5).ForEach(p =>
{
size.Width = Math.Max(size.Width, p.Width);
size.Height = Math.Max(size.Height, p.Height);
});
if (tempHandle.PageCount > 1)
size.Width += /*listThumbnails.ActualWidth*/ 150;
}
}
return new Size(size.Width * 3, size.Height * 3);
}
public void LoadPdf(string path)
{
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
PdfDocumentWrapper = new PdfDocumentWrapper(stream);
_pdfLoaded = true;
if (PdfDocumentWrapper.PdfDocument.PageCount < 2)
listThumbnails.Visibility = Visibility.Collapsed;
BeginLoadThumbnails();
}
public void LoadPdf(MemoryStream stream)
{
stream.Position = 0;
PdfDocumentWrapper = new PdfDocumentWrapper(stream);
_pdfLoaded = true;
if (PdfDocumentWrapper.PdfDocument.PageCount < 2)
listThumbnails.Visibility = Visibility.Collapsed;
BeginLoadThumbnails();
}
private void BeginLoadThumbnails()
{
Enumerable.Range(0, PdfDocumentWrapper.PdfDocument.PageCount).ForEach(PageThumbnails.Add);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@@ -1,106 +1,105 @@
// Copyright © 2017 Paddy Xu
//
//
// This file is part of QuickLook program.
//
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using QuickLook.Common.Plugin;
using System;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Windows.Threading;
using QuickLook.Common.Plugin;
namespace QuickLook.Plugin.PDFViewer
namespace QuickLook.Plugin.PDFViewer;
public class Plugin : IViewer
{
public class Plugin : IViewer
private ContextObject _context;
private string _path;
private PdfViewerControl _pdfControl;
public int Priority => 0;
public void Init()
{
private ContextObject _context;
private string _path;
private PdfViewerControl _pdfControl;
}
public int Priority => 0;
public bool CanHandle(string path)
{
if (Directory.Exists(path))
return false;
public void Init()
if (File.Exists(path) && Path.GetExtension(path).ToLower() == ".pdf")
return true;
using (var br = new BinaryReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
}
public bool CanHandle(string path)
{
if (Directory.Exists(path))
return false;
if (File.Exists(path) && Path.GetExtension(path).ToLower() == ".pdf")
return true;
using (var br = new BinaryReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
return Encoding.ASCII.GetString(br.ReadBytes(4)) == "%PDF";
}
}
public void Prepare(string path, ContextObject context)
{
_context = context;
_path = path;
var desiredSize = PdfViewerControl.GetDesiredControlSizeByFirstPage(path);
context.SetPreferredSizeFit(desiredSize, 0.9);
}
public void View(string path, ContextObject context)
{
_pdfControl = new PdfViewerControl();
context.ViewerContent = _pdfControl;
Exception exception = null;
_pdfControl.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
_pdfControl.LoadPdf(path);
context.Title = $"1 / {_pdfControl.TotalPages}: {Path.GetFileName(path)}";
_pdfControl.CurrentPageChanged += UpdateWindowCaption;
context.IsBusy = false;
}
catch (Exception e)
{
exception = e;
}
}), DispatcherPriority.Loaded).Wait();
if (exception != null)
ExceptionDispatchInfo.Capture(exception).Throw();
}
public void Cleanup()
{
GC.SuppressFinalize(this);
_pdfControl?.Dispose();
_pdfControl = null;
_context = null;
}
private void UpdateWindowCaption(object sender, EventArgs e2)
{
_context.Title = $"{_pdfControl.CurrentPage + 1} / {_pdfControl.TotalPages}: {Path.GetFileName(_path)}";
return Encoding.ASCII.GetString(br.ReadBytes(4)) == "%PDF";
}
}
}
public void Prepare(string path, ContextObject context)
{
_context = context;
_path = path;
var desiredSize = PdfViewerControl.GetDesiredControlSizeByFirstPage(path);
context.SetPreferredSizeFit(desiredSize, 0.9);
}
public void View(string path, ContextObject context)
{
_pdfControl = new PdfViewerControl();
context.ViewerContent = _pdfControl;
Exception exception = null;
_pdfControl.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
_pdfControl.LoadPdf(path);
context.Title = $"1 / {_pdfControl.TotalPages}: {Path.GetFileName(path)}";
_pdfControl.CurrentPageChanged += UpdateWindowCaption;
context.IsBusy = false;
}
catch (Exception e)
{
exception = e;
}
}), DispatcherPriority.Loaded).Wait();
if (exception != null)
ExceptionDispatchInfo.Capture(exception).Throw();
}
public void Cleanup()
{
GC.SuppressFinalize(this);
_pdfControl?.Dispose();
_pdfControl = null;
_context = null;
}
private void UpdateWindowCaption(object sender, EventArgs e2)
{
_context.Title = $"{_pdfControl.CurrentPage + 1} / {_pdfControl.TotalPages}: {Path.GetFileName(_path)}";
}
}